1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Core/Context.h"
26 #include "../Core/Profiler.h"
27 #include "../IO/File.h"
28 #include "../IO/FileSystem.h"
29 #include "../IO/Log.h"
30 #include "../Resource/Decompress.h"
31 
32 #include <JO/jo_jpeg.h>
33 #include <SDL/SDL_surface.h>
34 #define STB_IMAGE_IMPLEMENTATION
35 #include <STB/stb_image.h>
36 #define STB_IMAGE_WRITE_IMPLEMENTATION
37 #include <STB/stb_image_write.h>
38 #ifdef URHO3D_WEBP
39 #include <webp/decode.h>
40 #include <webp/encode.h>
41 #include <webp/mux.h>
42 #endif
43 
44 #include "../DebugNew.h"
45 
46 #ifndef MAKEFOURCC
47 #define MAKEFOURCC(ch0, ch1, ch2, ch3) ((unsigned)(ch0) | ((unsigned)(ch1) << 8) | ((unsigned)(ch2) << 16) | ((unsigned)(ch3) << 24))
48 #endif
49 
50 #define FOURCC_DXT1 (MAKEFOURCC('D','X','T','1'))
51 #define FOURCC_DXT2 (MAKEFOURCC('D','X','T','2'))
52 #define FOURCC_DXT3 (MAKEFOURCC('D','X','T','3'))
53 #define FOURCC_DXT4 (MAKEFOURCC('D','X','T','4'))
54 #define FOURCC_DXT5 (MAKEFOURCC('D','X','T','5'))
55 #define FOURCC_DX10 (MAKEFOURCC('D','X','1','0'))
56 
57 static const unsigned DDSCAPS_COMPLEX = 0x00000008U;
58 static const unsigned DDSCAPS_TEXTURE = 0x00001000U;
59 static const unsigned DDSCAPS_MIPMAP = 0x00400000U;
60 static const unsigned DDSCAPS2_VOLUME = 0x00200000U;
61 static const unsigned DDSCAPS2_CUBEMAP = 0x00000200U;
62 
63 static const unsigned DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400U;
64 static const unsigned DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800U;
65 static const unsigned DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000U;
66 static const unsigned DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000U;
67 static const unsigned DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000U;
68 static const unsigned DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000U;
69 static const unsigned DDSCAPS2_CUBEMAP_ALL_FACES = 0x0000FC00U;
70 
71 // DX10 flags
72 static const unsigned DDS_DIMENSION_TEXTURE1D = 2;
73 static const unsigned DDS_DIMENSION_TEXTURE2D = 3;
74 static const unsigned DDS_DIMENSION_TEXTURE3D = 4;
75 
76 static const unsigned DDS_RESOURCE_MISC_TEXTURECUBE = 0x4;
77 
78 static const unsigned DDS_DXGI_FORMAT_R8G8B8A8_UNORM = 28;
79 static const unsigned DDS_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 26;
80 static const unsigned DDS_DXGI_FORMAT_BC1_UNORM = 71;
81 static const unsigned DDS_DXGI_FORMAT_BC1_UNORM_SRGB = 72;
82 static const unsigned DDS_DXGI_FORMAT_BC2_UNORM = 74;
83 static const unsigned DDS_DXGI_FORMAT_BC2_UNORM_SRGB = 75;
84 static const unsigned DDS_DXGI_FORMAT_BC3_UNORM = 77;
85 static const unsigned DDS_DXGI_FORMAT_BC3_UNORM_SRGB = 78;
86 
87 namespace Urho3D
88 {
89 
90 /// DirectDraw color key definition.
91 struct DDColorKey
92 {
93     unsigned dwColorSpaceLowValue_;
94     unsigned dwColorSpaceHighValue_;
95 };
96 
97 /// DirectDraw pixel format definition.
98 struct DDPixelFormat
99 {
100     unsigned dwSize_;
101     unsigned dwFlags_;
102     unsigned dwFourCC_;
103     union
104     {
105         unsigned dwRGBBitCount_;
106         unsigned dwYUVBitCount_;
107         unsigned dwZBufferBitDepth_;
108         unsigned dwAlphaBitDepth_;
109         unsigned dwLuminanceBitCount_;
110         unsigned dwBumpBitCount_;
111         unsigned dwPrivateFormatBitCount_;
112     };
113     union
114     {
115         unsigned dwRBitMask_;
116         unsigned dwYBitMask_;
117         unsigned dwStencilBitDepth_;
118         unsigned dwLuminanceBitMask_;
119         unsigned dwBumpDuBitMask_;
120         unsigned dwOperations_;
121     };
122     union
123     {
124         unsigned dwGBitMask_;
125         unsigned dwUBitMask_;
126         unsigned dwZBitMask_;
127         unsigned dwBumpDvBitMask_;
128         struct
129         {
130             unsigned short wFlipMSTypes_;
131             unsigned short wBltMSTypes_;
132         } multiSampleCaps_;
133     };
134     union
135     {
136         unsigned dwBBitMask_;
137         unsigned dwVBitMask_;
138         unsigned dwStencilBitMask_;
139         unsigned dwBumpLuminanceBitMask_;
140     };
141     union
142     {
143         unsigned dwRGBAlphaBitMask_;
144         unsigned dwYUVAlphaBitMask_;
145         unsigned dwLuminanceAlphaBitMask_;
146         unsigned dwRGBZBitMask_;
147         unsigned dwYUVZBitMask_;
148     };
149 };
150 
151 /// DirectDraw surface capabilities.
152 struct DDSCaps2
153 {
154     unsigned dwCaps_;
155     unsigned dwCaps2_;
156     unsigned dwCaps3_;
157     union
158     {
159         unsigned dwCaps4_;
160         unsigned dwVolumeDepth_;
161     };
162 };
163 
164 struct DDSHeader10
165 {
166     unsigned dxgiFormat;
167     unsigned resourceDimension;
168     unsigned miscFlag;
169     unsigned arraySize;
170     unsigned reserved;
171 };
172 
173 /// DirectDraw surface description.
174 struct DDSurfaceDesc2
175 {
176     unsigned dwSize_;
177     unsigned dwFlags_;
178     unsigned dwHeight_;
179     unsigned dwWidth_;
180     union
181     {
182         unsigned lPitch_;
183         unsigned dwLinearSize_;
184     };
185     union
186     {
187         unsigned dwBackBufferCount_;
188         unsigned dwDepth_;
189     };
190     union
191     {
192         unsigned dwMipMapCount_;
193         unsigned dwRefreshRate_;
194         unsigned dwSrcVBHandle_;
195     };
196     unsigned dwAlphaBitDepth_;
197     unsigned dwReserved_;
198     unsigned lpSurface_; // Do not define as a void pointer, as it is 8 bytes in a 64bit build
199     union
200     {
201         DDColorKey ddckCKDestOverlay_;
202         unsigned dwEmptyFaceColor_;
203     };
204     DDColorKey ddckCKDestBlt_;
205     DDColorKey ddckCKSrcOverlay_;
206     DDColorKey ddckCKSrcBlt_;
207     union
208     {
209         DDPixelFormat ddpfPixelFormat_;
210         unsigned dwFVF_;
211     };
212     DDSCaps2 ddsCaps_;
213     unsigned dwTextureStage_;
214 };
215 
Decompress(unsigned char * dest)216 bool CompressedLevel::Decompress(unsigned char* dest)
217 {
218     if (!data_)
219         return false;
220 
221     switch (format_)
222     {
223     case CF_DXT1:
224     case CF_DXT3:
225     case CF_DXT5:
226         DecompressImageDXT(dest, data_, width_, height_, depth_, format_);
227         return true;
228 
229     case CF_ETC1:
230         DecompressImageETC(dest, data_, width_, height_);
231         return true;
232 
233     case CF_PVRTC_RGB_2BPP:
234     case CF_PVRTC_RGBA_2BPP:
235     case CF_PVRTC_RGB_4BPP:
236     case CF_PVRTC_RGBA_4BPP:
237         DecompressImagePVRTC(dest, data_, width_, height_, format_);
238         return true;
239 
240     default:
241         // Unknown format
242         return false;
243     }
244 }
245 
Image(Context * context)246 Image::Image(Context* context) :
247     Resource(context),
248     width_(0),
249     height_(0),
250     depth_(0),
251     components_(0),
252     numCompressedLevels_(0),
253     cubemap_(false),
254     array_(false),
255     sRGB_(false),
256     compressedFormat_(CF_NONE)
257 {
258 }
259 
~Image()260 Image::~Image()
261 {
262 }
263 
RegisterObject(Context * context)264 void Image::RegisterObject(Context* context)
265 {
266     context->RegisterFactory<Image>();
267 }
268 
BeginLoad(Deserializer & source)269 bool Image::BeginLoad(Deserializer& source)
270 {
271     // Check for DDS, KTX or PVR compressed format
272     String fileID = source.ReadFileID();
273 
274     if (fileID == "DDS ")
275     {
276         // DDS compressed format
277         DDSurfaceDesc2 ddsd;
278         source.Read(&ddsd, sizeof(ddsd));
279 
280         // DDS DX10+
281         const bool hasDXGI = ddsd.ddpfPixelFormat_.dwFourCC_ == FOURCC_DX10;
282         DDSHeader10 dxgiHeader;
283         if (hasDXGI)
284             source.Read(&dxgiHeader, sizeof(dxgiHeader));
285 
286         unsigned fourCC = ddsd.ddpfPixelFormat_.dwFourCC_;
287 
288         // If the DXGI header is available then remap formats and check sRGB
289         if (hasDXGI)
290         {
291             switch (dxgiHeader.dxgiFormat)
292             {
293             case DDS_DXGI_FORMAT_BC1_UNORM:
294             case DDS_DXGI_FORMAT_BC1_UNORM_SRGB:
295                 fourCC = FOURCC_DXT1;
296                 break;
297             case DDS_DXGI_FORMAT_BC2_UNORM:
298             case DDS_DXGI_FORMAT_BC2_UNORM_SRGB:
299                 fourCC = FOURCC_DXT3;
300                 break;
301             case DDS_DXGI_FORMAT_BC3_UNORM:
302             case DDS_DXGI_FORMAT_BC3_UNORM_SRGB:
303                 fourCC = FOURCC_DXT5;
304                 break;
305             case DDS_DXGI_FORMAT_R8G8B8A8_UNORM:
306             case DDS_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
307                 fourCC = 0;
308                 break;
309             default:
310                 URHO3D_LOGERROR("Unrecognized DDS DXGI image format");
311                 return false;
312             }
313 
314             // Check the internal sRGB formats
315             if (dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_BC1_UNORM_SRGB ||
316                 dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_BC2_UNORM_SRGB ||
317                 dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_BC3_UNORM_SRGB ||
318                 dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB)
319             {
320                 sRGB_ = true;
321             }
322         }
323         switch (fourCC)
324         {
325         case FOURCC_DXT1:
326             compressedFormat_ = CF_DXT1;
327             components_ = 3;
328             break;
329 
330         case FOURCC_DXT3:
331             compressedFormat_ = CF_DXT3;
332             components_ = 4;
333             break;
334 
335         case FOURCC_DXT5:
336             compressedFormat_ = CF_DXT5;
337             components_ = 4;
338             break;
339 
340         case 0:
341             if (ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 32 && ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 24 &&
342                 ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 16)
343             {
344                 URHO3D_LOGERROR("Unsupported DDS pixel byte size");
345                 return false;
346             }
347             compressedFormat_ = CF_RGBA;
348             components_ = 4;
349             break;
350 
351         default:
352             URHO3D_LOGERROR("Unrecognized DDS image format");
353             return false;
354         }
355 
356         // Is it a cube map or texture array? If so determine the size of the image chain.
357         cubemap_ = (ddsd.ddsCaps_.dwCaps2_ & DDSCAPS2_CUBEMAP_ALL_FACES) != 0 || (hasDXGI && (dxgiHeader.miscFlag & DDS_RESOURCE_MISC_TEXTURECUBE) != 0);
358         unsigned imageChainCount = 1;
359         if (cubemap_)
360             imageChainCount = 6;
361         else if (hasDXGI && dxgiHeader.arraySize > 1)
362         {
363             imageChainCount = dxgiHeader.arraySize;
364             array_ = true;
365         }
366 
367         // Calculate the size of the data
368         unsigned dataSize = 0;
369         if (compressedFormat_ != CF_RGBA)
370         {
371             const unsigned blockSize = compressedFormat_ == CF_DXT1 ? 8 : 16; //DXT1/BC1 is 8 bytes, DXT3/BC2 and DXT5/BC3 are 16 bytes
372             // Add 3 to ensure valid block: ie 2x2 fits uses a whole 4x4 block
373             unsigned blocksWide = (ddsd.dwWidth_ + 3) / 4;
374             unsigned blocksHeight = (ddsd.dwHeight_ + 3) / 4;
375             dataSize = blocksWide * blocksHeight * blockSize;
376 
377             // Calculate mip data size
378             unsigned x = ddsd.dwWidth_ / 2;
379             unsigned y = ddsd.dwHeight_ / 2;
380             unsigned z = ddsd.dwDepth_ / 2;
381             for (unsigned level = ddsd.dwMipMapCount_; level > 1; x /= 2, y /= 2, z /= 2, --level)
382             {
383                 blocksWide = (Max(x, 1U) + 3) / 4;
384                 blocksHeight = (Max(y, 1U) + 3) / 4;
385                 dataSize += blockSize * blocksWide * blocksHeight * Max(z, 1U);
386             }
387         }
388         else
389         {
390             dataSize = (ddsd.ddpfPixelFormat_.dwRGBBitCount_ / 8) * ddsd.dwWidth_ * ddsd.dwHeight_ * Max(ddsd.dwDepth_, 1U);
391             // Calculate mip data size
392             unsigned x = ddsd.dwWidth_ / 2;
393             unsigned y = ddsd.dwHeight_ / 2;
394             unsigned z = ddsd.dwDepth_ / 2;
395             for (unsigned level = ddsd.dwMipMapCount_; level > 1; x /= 2, y /= 2, z /= 2, --level)
396                 dataSize += (ddsd.ddpfPixelFormat_.dwRGBBitCount_ / 8) * Max(x, 1U) * Max(y, 1U) * Max(z, 1U);
397         }
398 
399         // Do not use a shared ptr here, in case nothing is refcounting the image outside this function.
400         // A raw pointer is fine as the image chain (if needed) uses shared ptr's properly
401         Image* currentImage = this;
402 
403         for (unsigned faceIndex = 0; faceIndex < imageChainCount; ++faceIndex)
404         {
405             currentImage->data_ = new unsigned char[dataSize];
406             currentImage->cubemap_ = cubemap_;
407             currentImage->array_ = array_;
408             currentImage->components_ = components_;
409             currentImage->compressedFormat_ = compressedFormat_;
410             currentImage->width_ = ddsd.dwWidth_;
411             currentImage->height_ = ddsd.dwHeight_;
412             currentImage->depth_ = ddsd.dwDepth_;
413 
414             currentImage->numCompressedLevels_ = ddsd.dwMipMapCount_;
415             if (!currentImage->numCompressedLevels_)
416                 currentImage->numCompressedLevels_ = 1;
417 
418             // Memory use needs to be exact per image as it's used for verifying the data size in GetCompressedLevel()
419             // even though it would be more proper for the first image to report the size of all siblings combined
420             currentImage->SetMemoryUse(dataSize);
421 
422             source.Read(currentImage->data_.Get(), dataSize);
423 
424             if (faceIndex < imageChainCount - 1)
425             {
426                 // Build the image chain
427                 SharedPtr<Image> nextImage(new Image(context_));
428                 currentImage->nextSibling_ = nextImage;
429                 currentImage = nextImage;
430             }
431         }
432 
433         // If uncompressed DDS, convert the data to 8bit RGBA as the texture classes can not currently use eg. RGB565 format
434         if (compressedFormat_ == CF_RGBA)
435         {
436             URHO3D_PROFILE(ConvertDDSToRGBA);
437 
438             currentImage = this;
439 
440             while (currentImage)
441             {
442                 unsigned sourcePixelByteSize = ddsd.ddpfPixelFormat_.dwRGBBitCount_ >> 3;
443                 unsigned numPixels = dataSize / sourcePixelByteSize;
444 
445 #define ADJUSTSHIFT(mask, l, r) \
446                 if (mask && mask >= 0x100) \
447                 { \
448                     while ((mask >> r) >= 0x100) \
449                     ++r; \
450                 } \
451                 else if (mask && mask < 0x80) \
452                 { \
453                     while ((mask << l) < 0x80) \
454                     ++l; \
455                 }
456 
457                 unsigned rShiftL = 0, gShiftL = 0, bShiftL = 0, aShiftL = 0;
458                 unsigned rShiftR = 0, gShiftR = 0, bShiftR = 0, aShiftR = 0;
459                 unsigned rMask = ddsd.ddpfPixelFormat_.dwRBitMask_;
460                 unsigned gMask = ddsd.ddpfPixelFormat_.dwGBitMask_;
461                 unsigned bMask = ddsd.ddpfPixelFormat_.dwBBitMask_;
462                 unsigned aMask = ddsd.ddpfPixelFormat_.dwRGBAlphaBitMask_;
463                 ADJUSTSHIFT(rMask, rShiftL, rShiftR)
464                 ADJUSTSHIFT(gMask, gShiftL, gShiftR)
465                 ADJUSTSHIFT(bMask, bShiftL, bShiftR)
466                 ADJUSTSHIFT(aMask, aShiftL, aShiftR)
467 
468                 SharedArrayPtr<unsigned char> rgbaData(new unsigned char[numPixels * 4]);
469 
470                 switch (sourcePixelByteSize)
471                 {
472                 case 4:
473                 {
474                     unsigned* src = (unsigned*)currentImage->data_.Get();
475                     unsigned char* dest = rgbaData.Get();
476 
477                     while (numPixels--)
478                     {
479                         unsigned pixels = *src++;
480                         *dest++ = ((pixels & rMask) << rShiftL) >> rShiftR;
481                         *dest++ = ((pixels & gMask) << gShiftL) >> gShiftR;
482                         *dest++ = ((pixels & bMask) << bShiftL) >> bShiftR;
483                         *dest++ = ((pixels & aMask) << aShiftL) >> aShiftR;
484                     }
485                 }
486                 break;
487 
488                 case 3:
489                 {
490                     unsigned char* src = currentImage->data_.Get();
491                     unsigned char* dest = rgbaData.Get();
492 
493                     while (numPixels--)
494                     {
495                         unsigned pixels = src[0] | (src[1] << 8) | (src[2] << 16);
496                         src += 3;
497                         *dest++ = ((pixels & rMask) << rShiftL) >> rShiftR;
498                         *dest++ = ((pixels & gMask) << gShiftL) >> gShiftR;
499                         *dest++ = ((pixels & bMask) << bShiftL) >> bShiftR;
500                         *dest++ = ((pixels & aMask) << aShiftL) >> aShiftR;
501                     }
502                 }
503                 break;
504 
505                 default:
506                 {
507                     unsigned short* src = (unsigned short*)currentImage->data_.Get();
508                     unsigned char* dest = rgbaData.Get();
509 
510                     while (numPixels--)
511                     {
512                         unsigned short pixels = *src++;
513                         *dest++ = ((pixels & rMask) << rShiftL) >> rShiftR;
514                         *dest++ = ((pixels & gMask) << gShiftL) >> gShiftR;
515                         *dest++ = ((pixels & bMask) << bShiftL) >> bShiftR;
516                         *dest++ = ((pixels & aMask) << aShiftL) >> aShiftR;
517                     }
518                 }
519                 break;
520                 }
521 
522                 // Replace with converted data
523                 currentImage->data_ = rgbaData;
524                 currentImage->SetMemoryUse(numPixels * 4);
525                 currentImage = currentImage->GetNextSibling();
526             }
527         }
528     }
529     else if (fileID == "\253KTX")
530     {
531         source.Seek(12);
532 
533         unsigned endianness = source.ReadUInt();
534         unsigned type = source.ReadUInt();
535         /* unsigned typeSize = */ source.ReadUInt();
536         unsigned format = source.ReadUInt();
537         unsigned internalFormat = source.ReadUInt();
538         /* unsigned baseInternalFormat = */ source.ReadUInt();
539         unsigned width = source.ReadUInt();
540         unsigned height = source.ReadUInt();
541         unsigned depth = source.ReadUInt();
542         /* unsigned arrayElements = */ source.ReadUInt();
543         unsigned faces = source.ReadUInt();
544         unsigned mipmaps = source.ReadUInt();
545         unsigned keyValueBytes = source.ReadUInt();
546 
547         if (endianness != 0x04030201)
548         {
549             URHO3D_LOGERROR("Big-endian KTX files not supported");
550             return false;
551         }
552 
553         if (type != 0 || format != 0)
554         {
555             URHO3D_LOGERROR("Uncompressed KTX files not supported");
556             return false;
557         }
558 
559         if (faces > 1 || depth > 1)
560         {
561             URHO3D_LOGERROR("3D or cube KTX files not supported");
562             return false;
563         }
564 
565         if (mipmaps == 0)
566         {
567             URHO3D_LOGERROR("KTX files without explicitly specified mipmap count not supported");
568             return false;
569         }
570 
571         switch (internalFormat)
572         {
573         case 0x83f1:
574             compressedFormat_ = CF_DXT1;
575             components_ = 4;
576             break;
577 
578         case 0x83f2:
579             compressedFormat_ = CF_DXT3;
580             components_ = 4;
581             break;
582 
583         case 0x83f3:
584             compressedFormat_ = CF_DXT5;
585             components_ = 4;
586             break;
587 
588         case 0x8d64:
589             compressedFormat_ = CF_ETC1;
590             components_ = 3;
591             break;
592 
593         case 0x8c00:
594             compressedFormat_ = CF_PVRTC_RGB_4BPP;
595             components_ = 3;
596             break;
597 
598         case 0x8c01:
599             compressedFormat_ = CF_PVRTC_RGB_2BPP;
600             components_ = 3;
601             break;
602 
603         case 0x8c02:
604             compressedFormat_ = CF_PVRTC_RGBA_4BPP;
605             components_ = 4;
606             break;
607 
608         case 0x8c03:
609             compressedFormat_ = CF_PVRTC_RGBA_2BPP;
610             components_ = 4;
611             break;
612 
613         default:
614             compressedFormat_ = CF_NONE;
615             break;
616         }
617 
618         if (compressedFormat_ == CF_NONE)
619         {
620             URHO3D_LOGERROR("Unsupported texture format in KTX file");
621             return false;
622         }
623 
624         source.Seek(source.GetPosition() + keyValueBytes);
625         unsigned dataSize = (unsigned)(source.GetSize() - source.GetPosition() - mipmaps * sizeof(unsigned));
626 
627         data_ = new unsigned char[dataSize];
628         width_ = width;
629         height_ = height;
630         numCompressedLevels_ = mipmaps;
631 
632         unsigned dataOffset = 0;
633         for (unsigned i = 0; i < mipmaps; ++i)
634         {
635             unsigned levelSize = source.ReadUInt();
636             if (levelSize + dataOffset > dataSize)
637             {
638                 URHO3D_LOGERROR("KTX mipmap level data size exceeds file size");
639                 return false;
640             }
641 
642             source.Read(&data_[dataOffset], levelSize);
643             dataOffset += levelSize;
644             if (source.GetPosition() & 3)
645                 source.Seek((source.GetPosition() + 3) & 0xfffffffc);
646         }
647 
648         SetMemoryUse(dataSize);
649     }
650     else if (fileID == "PVR\3")
651     {
652         /* unsigned flags = */ source.ReadUInt();
653         unsigned pixelFormatLo = source.ReadUInt();
654         /* unsigned pixelFormatHi = */ source.ReadUInt();
655         /* unsigned colourSpace = */ source.ReadUInt();
656         /* unsigned channelType = */ source.ReadUInt();
657         unsigned height = source.ReadUInt();
658         unsigned width = source.ReadUInt();
659         unsigned depth = source.ReadUInt();
660         /* unsigned numSurfaces = */ source.ReadUInt();
661         unsigned numFaces = source.ReadUInt();
662         unsigned mipmapCount = source.ReadUInt();
663         unsigned metaDataSize = source.ReadUInt();
664 
665         if (depth > 1 || numFaces > 1)
666         {
667             URHO3D_LOGERROR("3D or cube PVR files not supported");
668             return false;
669         }
670 
671         if (mipmapCount == 0)
672         {
673             URHO3D_LOGERROR("PVR files without explicitly specified mipmap count not supported");
674             return false;
675         }
676 
677         switch (pixelFormatLo)
678         {
679         case 0:
680             compressedFormat_ = CF_PVRTC_RGB_2BPP;
681             components_ = 3;
682             break;
683 
684         case 1:
685             compressedFormat_ = CF_PVRTC_RGBA_2BPP;
686             components_ = 4;
687             break;
688 
689         case 2:
690             compressedFormat_ = CF_PVRTC_RGB_4BPP;
691             components_ = 3;
692             break;
693 
694         case 3:
695             compressedFormat_ = CF_PVRTC_RGBA_4BPP;
696             components_ = 4;
697             break;
698 
699         case 6:
700             compressedFormat_ = CF_ETC1;
701             components_ = 3;
702             break;
703 
704         case 7:
705             compressedFormat_ = CF_DXT1;
706             components_ = 4;
707             break;
708 
709         case 9:
710             compressedFormat_ = CF_DXT3;
711             components_ = 4;
712             break;
713 
714         case 11:
715             compressedFormat_ = CF_DXT5;
716             components_ = 4;
717             break;
718 
719         default:
720             compressedFormat_ = CF_NONE;
721             break;
722         }
723 
724         if (compressedFormat_ == CF_NONE)
725         {
726             URHO3D_LOGERROR("Unsupported texture format in PVR file");
727             return false;
728         }
729 
730         source.Seek(source.GetPosition() + metaDataSize);
731         unsigned dataSize = source.GetSize() - source.GetPosition();
732 
733         data_ = new unsigned char[dataSize];
734         width_ = width;
735         height_ = height;
736         numCompressedLevels_ = mipmapCount;
737 
738         source.Read(data_.Get(), dataSize);
739         SetMemoryUse(dataSize);
740     }
741 #ifdef URHO3D_WEBP
742     else if (fileID == "RIFF")
743     {
744         // WebP: https://developers.google.com/speed/webp/docs/api
745 
746         // RIFF layout is:
747         //   Offset  tag
748         //   0...3   "RIFF" 4-byte tag
749         //   4...7   size of image data (including metadata) starting at offset 8
750         //   8...11  "WEBP"   our form-type signature
751         const uint8_t TAG_SIZE(4);
752 
753         source.Seek(8);
754         uint8_t fourCC[TAG_SIZE];
755         memset(&fourCC, 0, sizeof(uint8_t) * TAG_SIZE);
756 
757         unsigned bytesRead(source.Read(&fourCC, TAG_SIZE));
758         if (bytesRead != TAG_SIZE)
759         {
760             // Truncated.
761             URHO3D_LOGERROR("Truncated RIFF data");
762             return false;
763         }
764         const uint8_t WEBP[TAG_SIZE] = {'W', 'E', 'B', 'P'};
765         if (memcmp(fourCC, WEBP, TAG_SIZE))
766         {
767             // VP8_STATUS_BITSTREAM_ERROR
768             URHO3D_LOGERROR("Invalid header");
769             return false;
770         }
771 
772         // Read the file to buffer.
773         size_t dataSize(source.GetSize());
774         SharedArrayPtr<uint8_t> data(new uint8_t[dataSize]);
775 
776         memset(data.Get(), 0, sizeof(uint8_t) * dataSize);
777         source.Seek(0);
778         source.Read(data.Get(), dataSize);
779 
780         WebPBitstreamFeatures features;
781 
782         if (WebPGetFeatures(data.Get(), dataSize, &features) != VP8_STATUS_OK)
783         {
784             URHO3D_LOGERROR("Error reading WebP image: " + source.GetName());
785             return false;
786         }
787 
788         size_t imgSize(features.width * features.height * (features.has_alpha ? 4 : 3));
789         SharedArrayPtr<uint8_t> pixelData(new uint8_t[imgSize]);
790 
791         bool decodeError(false);
792         if (features.has_alpha)
793         {
794             decodeError = WebPDecodeRGBAInto(data.Get(), dataSize, pixelData.Get(), imgSize, 4 * features.width) == NULL;
795         }
796         else
797         {
798             decodeError = WebPDecodeRGBInto(data.Get(), dataSize, pixelData.Get(), imgSize, 3 * features.width) == NULL;
799         }
800         if (decodeError)
801         {
802             URHO3D_LOGERROR("Error decoding WebP image:" + source.GetName());
803             return false;
804         }
805 
806         SetSize(features.width, features.height, features.has_alpha ? 4 : 3);
807         SetData(pixelData);
808     }
809 #endif
810     else
811     {
812         // Not DDS, KTX or PVR, use STBImage to load other image formats as uncompressed
813         source.Seek(0);
814         int width, height;
815         unsigned components;
816         unsigned char* pixelData = GetImageData(source, width, height, components);
817         if (!pixelData)
818         {
819             URHO3D_LOGERROR("Could not load image " + source.GetName() + ": " + String(stbi_failure_reason()));
820             return false;
821         }
822         SetSize(width, height, components);
823         SetData(pixelData);
824         FreeImageData(pixelData);
825     }
826 
827     return true;
828 }
829 
Save(Serializer & dest) const830 bool Image::Save(Serializer& dest) const
831 {
832     URHO3D_PROFILE(SaveImage);
833 
834     if (IsCompressed())
835     {
836         URHO3D_LOGERROR("Can not save compressed image " + GetName());
837         return false;
838     }
839 
840     if (!data_)
841     {
842         URHO3D_LOGERROR("Can not save zero-sized image " + GetName());
843         return false;
844     }
845 
846     int len;
847     unsigned char* png = stbi_write_png_to_mem(data_.Get(), 0, width_, height_, components_, &len);
848     bool success = dest.Write(png, (unsigned)len) == (unsigned)len;
849     free(png);
850     return success;
851 }
852 
SaveFile(const String & fileName) const853 bool Image::SaveFile(const String& fileName) const
854 {
855     if (fileName.EndsWith(".dds", false))
856         return SaveDDS(fileName);
857     else if (fileName.EndsWith(".bmp", false))
858         return SaveBMP(fileName);
859     else if (fileName.EndsWith(".jpg", false) || fileName.EndsWith(".jpeg", false))
860         return SaveJPG(fileName, 100);
861     else if (fileName.EndsWith(".tga", false))
862         return SaveTGA(fileName);
863 #ifdef URHO3D_WEBP
864     else if (fileName.EndsWith(".webp", false))
865         return SaveWEBP(fileName, 100.0f);
866 #endif
867     else
868         return SavePNG(fileName);
869 }
870 
SetSize(int width,int height,unsigned components)871 bool Image::SetSize(int width, int height, unsigned components)
872 {
873     return SetSize(width, height, 1, components);
874 }
875 
SetSize(int width,int height,int depth,unsigned components)876 bool Image::SetSize(int width, int height, int depth, unsigned components)
877 {
878     if (width == width_ && height == height_ && depth == depth_ && components == components_)
879         return true;
880 
881     if (width <= 0 || height <= 0 || depth <= 0)
882         return false;
883 
884     if (components > 4)
885     {
886         URHO3D_LOGERROR("More than 4 color components are not supported");
887         return false;
888     }
889 
890     data_ = new unsigned char[width * height * depth * components];
891     width_ = width;
892     height_ = height;
893     depth_ = depth;
894     components_ = components;
895     compressedFormat_ = CF_NONE;
896     numCompressedLevels_ = 0;
897     nextLevel_.Reset();
898 
899     SetMemoryUse(width * height * depth * components);
900     return true;
901 }
902 
SetPixel(int x,int y,const Color & color)903 void Image::SetPixel(int x, int y, const Color& color)
904 {
905     SetPixelInt(x, y, 0, color.ToUInt());
906 }
907 
SetPixel(int x,int y,int z,const Color & color)908 void Image::SetPixel(int x, int y, int z, const Color& color)
909 {
910     SetPixelInt(x, y, z, color.ToUInt());
911 }
912 
SetPixelInt(int x,int y,unsigned uintColor)913 void Image::SetPixelInt(int x, int y, unsigned uintColor)
914 {
915     SetPixelInt(x, y, 0, uintColor);
916 }
917 
SetPixelInt(int x,int y,int z,unsigned uintColor)918 void Image::SetPixelInt(int x, int y, int z, unsigned uintColor)
919 {
920     if (!data_ || x < 0 || x >= width_ || y < 0 || y >= height_ || z < 0 || z >= depth_ || IsCompressed())
921         return;
922 
923     unsigned char* dest = data_ + (z * width_ * height_ + y * width_ + x) * components_;
924     unsigned char* src = (unsigned char*)&uintColor;
925 
926     switch (components_)
927     {
928     case 4:
929         dest[3] = src[3];
930         // Fall through
931     case 3:
932         dest[2] = src[2];
933         // Fall through
934     case 2:
935         dest[1] = src[1];
936         // Fall through
937     default:
938         dest[0] = src[0];
939         break;
940     }
941 }
942 
SetData(const unsigned char * pixelData)943 void Image::SetData(const unsigned char* pixelData)
944 {
945     if (!data_)
946         return;
947 
948     if (IsCompressed())
949     {
950         URHO3D_LOGERROR("Can not set new pixel data for a compressed image");
951         return;
952     }
953 
954     memcpy(data_.Get(), pixelData, width_ * height_ * depth_ * components_);
955     nextLevel_.Reset();
956 }
957 
LoadColorLUT(Deserializer & source)958 bool Image::LoadColorLUT(Deserializer& source)
959 {
960     String fileID = source.ReadFileID();
961 
962     if (fileID == "DDS " || fileID == "\253KTX" || fileID == "PVR\3")
963     {
964         URHO3D_LOGERROR("Invalid image format, can not load image");
965         return false;
966     }
967 
968     source.Seek(0);
969     int width, height;
970     unsigned components;
971     unsigned char* pixelDataIn = GetImageData(source, width, height, components);
972     if (!pixelDataIn)
973     {
974         URHO3D_LOGERROR("Could not load image " + source.GetName() + ": " + String(stbi_failure_reason()));
975         return false;
976     }
977     if (components != 3)
978     {
979         URHO3D_LOGERROR("Invalid image format, can not load image");
980         return false;
981     }
982 
983     SetSize(COLOR_LUT_SIZE, COLOR_LUT_SIZE, COLOR_LUT_SIZE, components);
984     SetMemoryUse(width_ * height_ * depth_ * components);
985 
986     unsigned char* pixelDataOut = GetData();
987 
988     for (int z = 0; z < depth_; ++z)
989     {
990         for (int y = 0; y < height_; ++y)
991         {
992             unsigned char* in = &pixelDataIn[z * width_ * 3 + y * width * 3];
993             unsigned char* out = &pixelDataOut[z * width_ * height_ * 3 + y * width_ * 3];
994 
995             for (int x = 0; x < width_ * 3; x += 3)
996             {
997                 out[x] = in[x];
998                 out[x + 1] = in[x + 1];
999                 out[x + 2] = in[x + 2];
1000             }
1001         }
1002     }
1003 
1004     FreeImageData(pixelDataIn);
1005 
1006     return true;
1007 }
1008 
FlipHorizontal()1009 bool Image::FlipHorizontal()
1010 {
1011     if (!data_)
1012         return false;
1013 
1014     if (depth_ > 1)
1015     {
1016         URHO3D_LOGERROR("FlipHorizontal not supported for 3D images");
1017         return false;
1018     }
1019 
1020     if (!IsCompressed())
1021     {
1022         SharedArrayPtr<unsigned char> newData(new unsigned char[width_ * height_ * components_]);
1023         unsigned rowSize = width_ * components_;
1024 
1025         for (int y = 0; y < height_; ++y)
1026         {
1027             for (int x = 0; x < width_; ++x)
1028             {
1029                 for (unsigned c = 0; c < components_; ++c)
1030                     newData[y * rowSize + x * components_ + c] = data_[y * rowSize + (width_ - x - 1) * components_ + c];
1031             }
1032         }
1033 
1034         data_ = newData;
1035     }
1036     else
1037     {
1038         if (compressedFormat_ > CF_DXT5)
1039         {
1040             URHO3D_LOGERROR("FlipHorizontal not yet implemented for other compressed formats than RGBA & DXT1,3,5");
1041             return false;
1042         }
1043 
1044         // Memory use = combined size of the compressed mip levels
1045         SharedArrayPtr<unsigned char> newData(new unsigned char[GetMemoryUse()]);
1046         unsigned dataOffset = 0;
1047 
1048         for (unsigned i = 0; i < numCompressedLevels_; ++i)
1049         {
1050             CompressedLevel level = GetCompressedLevel(i);
1051             if (!level.data_)
1052             {
1053                 URHO3D_LOGERROR("Got compressed level with no data, aborting horizontal flip");
1054                 return false;
1055             }
1056 
1057             for (unsigned y = 0; y < level.rows_; ++y)
1058             {
1059                 for (unsigned x = 0; x < level.rowSize_; x += level.blockSize_)
1060                 {
1061                     unsigned char* src = level.data_ + y * level.rowSize_ + (level.rowSize_ - level.blockSize_ - x);
1062                     unsigned char* dest = newData.Get() + y * level.rowSize_ + x;
1063                     FlipBlockHorizontal(dest, src, compressedFormat_);
1064                 }
1065             }
1066 
1067             dataOffset += level.dataSize_;
1068         }
1069 
1070         data_ = newData;
1071     }
1072 
1073     return true;
1074 }
1075 
FlipVertical()1076 bool Image::FlipVertical()
1077 {
1078     if (!data_)
1079         return false;
1080 
1081     if (depth_ > 1)
1082     {
1083         URHO3D_LOGERROR("FlipVertical not supported for 3D images");
1084         return false;
1085     }
1086 
1087     if (!IsCompressed())
1088     {
1089         SharedArrayPtr<unsigned char> newData(new unsigned char[width_ * height_ * components_]);
1090         unsigned rowSize = width_ * components_;
1091 
1092         for (int y = 0; y < height_; ++y)
1093             memcpy(&newData[(height_ - y - 1) * rowSize], &data_[y * rowSize], rowSize);
1094 
1095         data_ = newData;
1096     }
1097     else
1098     {
1099         if (compressedFormat_ > CF_DXT5)
1100         {
1101             URHO3D_LOGERROR("FlipVertical not yet implemented for other compressed formats than DXT1,3,5");
1102             return false;
1103         }
1104 
1105         // Memory use = combined size of the compressed mip levels
1106         SharedArrayPtr<unsigned char> newData(new unsigned char[GetMemoryUse()]);
1107         unsigned dataOffset = 0;
1108 
1109         for (unsigned i = 0; i < numCompressedLevels_; ++i)
1110         {
1111             CompressedLevel level = GetCompressedLevel(i);
1112             if (!level.data_)
1113             {
1114                 URHO3D_LOGERROR("Got compressed level with no data, aborting vertical flip");
1115                 return false;
1116             }
1117 
1118             for (unsigned y = 0; y < level.rows_; ++y)
1119             {
1120                 unsigned char* src = level.data_ + y * level.rowSize_;
1121                 unsigned char* dest = newData.Get() + dataOffset + (level.rows_ - y - 1) * level.rowSize_;
1122 
1123                 for (unsigned x = 0; x < level.rowSize_; x += level.blockSize_)
1124                     FlipBlockVertical(dest + x, src + x, compressedFormat_);
1125             }
1126 
1127             dataOffset += level.dataSize_;
1128         }
1129 
1130         data_ = newData;
1131     }
1132 
1133     return true;
1134 }
1135 
Resize(int width,int height)1136 bool Image::Resize(int width, int height)
1137 {
1138     URHO3D_PROFILE(ResizeImage);
1139 
1140     if (IsCompressed())
1141     {
1142         URHO3D_LOGERROR("Resize not supported for compressed images");
1143         return false;
1144     }
1145 
1146     if (depth_ > 1)
1147     {
1148         URHO3D_LOGERROR("Resize not supported for 3D images");
1149         return false;
1150     }
1151 
1152     if (!data_ || width <= 0 || height <= 0)
1153         return false;
1154 
1155     /// \todo Reducing image size does not sample all needed pixels
1156     SharedArrayPtr<unsigned char> newData(new unsigned char[width * height * components_]);
1157     for (int y = 0; y < height; ++y)
1158     {
1159         for (int x = 0; x < width; ++x)
1160         {
1161             // Calculate float coordinates between 0 - 1 for resampling
1162             float xF = (width_ > 1) ? (float)x / (float)(width - 1) : 0.0f;
1163             float yF = (height_ > 1) ? (float)y / (float)(height - 1) : 0.0f;
1164             unsigned uintColor = GetPixelBilinear(xF, yF).ToUInt();
1165             unsigned char* dest = newData + (y * width + x) * components_;
1166             unsigned char* src = (unsigned char*)&uintColor;
1167 
1168             switch (components_)
1169             {
1170             case 4:
1171                 dest[3] = src[3];
1172                 // Fall through
1173             case 3:
1174                 dest[2] = src[2];
1175                 // Fall through
1176             case 2:
1177                 dest[1] = src[1];
1178                 // Fall through
1179             default:
1180                 dest[0] = src[0];
1181                 break;
1182             }
1183         }
1184     }
1185 
1186     width_ = width;
1187     height_ = height;
1188     data_ = newData;
1189     SetMemoryUse(width * height * depth_ * components_);
1190     return true;
1191 }
1192 
Clear(const Color & color)1193 void Image::Clear(const Color& color)
1194 {
1195     ClearInt(color.ToUInt());
1196 }
1197 
ClearInt(unsigned uintColor)1198 void Image::ClearInt(unsigned uintColor)
1199 {
1200     URHO3D_PROFILE(ClearImage);
1201 
1202     if (!data_)
1203         return;
1204 
1205     if (IsCompressed())
1206     {
1207         URHO3D_LOGERROR("Clear not supported for compressed images");
1208         return;
1209     }
1210 
1211     if (components_ == 4)
1212     {
1213         unsigned color = uintColor;
1214         unsigned* data = (unsigned*)GetData();
1215         unsigned* data_end = (unsigned*)(GetData() + width_ * height_ * depth_ * components_);
1216         for (; data < data_end; ++data)
1217             *data = color;
1218     }
1219     else
1220     {
1221         unsigned char* src = (unsigned char*)&uintColor;
1222         for (unsigned i = 0; i < width_ * height_ * depth_ * components_; ++i)
1223             data_[i] = src[i % components_];
1224     }
1225 }
1226 
SaveBMP(const String & fileName) const1227 bool Image::SaveBMP(const String& fileName) const
1228 {
1229     URHO3D_PROFILE(SaveImageBMP);
1230 
1231     FileSystem* fileSystem = GetSubsystem<FileSystem>();
1232     if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
1233     {
1234         URHO3D_LOGERROR("Access denied to " + fileName);
1235         return false;
1236     }
1237 
1238     if (IsCompressed())
1239     {
1240         URHO3D_LOGERROR("Can not save compressed image to BMP");
1241         return false;
1242     }
1243 
1244     if (data_)
1245         return stbi_write_bmp(fileName.CString(), width_, height_, components_, data_.Get()) != 0;
1246     else
1247         return false;
1248 }
1249 
SavePNG(const String & fileName) const1250 bool Image::SavePNG(const String& fileName) const
1251 {
1252     URHO3D_PROFILE(SaveImagePNG);
1253 
1254     File outFile(context_, fileName, FILE_WRITE);
1255     if (outFile.IsOpen())
1256         return Image::Save(outFile); // Save uses PNG format
1257     else
1258         return false;
1259 }
1260 
SaveTGA(const String & fileName) const1261 bool Image::SaveTGA(const String& fileName) const
1262 {
1263     URHO3D_PROFILE(SaveImageTGA);
1264 
1265     FileSystem* fileSystem = GetSubsystem<FileSystem>();
1266     if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
1267     {
1268         URHO3D_LOGERROR("Access denied to " + fileName);
1269         return false;
1270     }
1271 
1272     if (IsCompressed())
1273     {
1274         URHO3D_LOGERROR("Can not save compressed image to TGA");
1275         return false;
1276     }
1277 
1278     if (data_)
1279         return stbi_write_tga(GetNativePath(fileName).CString(), width_, height_, components_, data_.Get()) != 0;
1280     else
1281         return false;
1282 }
1283 
SaveJPG(const String & fileName,int quality) const1284 bool Image::SaveJPG(const String& fileName, int quality) const
1285 {
1286     URHO3D_PROFILE(SaveImageJPG);
1287 
1288     FileSystem* fileSystem = GetSubsystem<FileSystem>();
1289     if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
1290     {
1291         URHO3D_LOGERROR("Access denied to " + fileName);
1292         return false;
1293     }
1294 
1295     if (IsCompressed())
1296     {
1297         URHO3D_LOGERROR("Can not save compressed image to JPG");
1298         return false;
1299     }
1300 
1301     if (data_)
1302         return jo_write_jpg(GetNativePath(fileName).CString(), data_.Get(), width_, height_, components_, quality) != 0;
1303     else
1304         return false;
1305 }
1306 
SaveDDS(const String & fileName) const1307 bool Image::SaveDDS(const String& fileName) const
1308 {
1309     URHO3D_PROFILE(SaveImageDDS);
1310 
1311     File outFile(context_, fileName, FILE_WRITE);
1312     if (!outFile.IsOpen())
1313     {
1314         URHO3D_LOGERROR("Access denied to " + fileName);
1315         return false;
1316     }
1317 
1318     if (IsCompressed())
1319     {
1320         URHO3D_LOGERROR("Can not save compressed image to DDS");
1321         return false;
1322     }
1323 
1324     if (components_ != 4)
1325     {
1326         URHO3D_LOGERRORF("Can not save image with %u components to DDS", components_);
1327         return false;
1328     }
1329 
1330     // Write image
1331     PODVector<const Image*> levels;
1332     GetLevels(levels);
1333 
1334     outFile.WriteFileID("DDS ");
1335 
1336     DDSurfaceDesc2 ddsd;
1337     memset(&ddsd, 0, sizeof(ddsd));
1338     ddsd.dwSize_ = sizeof(ddsd);
1339     ddsd.dwFlags_ = 0x00000001l /*DDSD_CAPS*/
1340         | 0x00000002l /*DDSD_HEIGHT*/ | 0x00000004l /*DDSD_WIDTH*/ | 0x00020000l /*DDSD_MIPMAPCOUNT*/ | 0x00001000l /*DDSD_PIXELFORMAT*/;
1341     ddsd.dwWidth_ = width_;
1342     ddsd.dwHeight_ = height_;
1343     ddsd.dwMipMapCount_ = levels.Size();
1344     ddsd.ddpfPixelFormat_.dwFlags_ = 0x00000040l /*DDPF_RGB*/ | 0x00000001l /*DDPF_ALPHAPIXELS*/;
1345     ddsd.ddpfPixelFormat_.dwSize_ = sizeof(ddsd.ddpfPixelFormat_);
1346     ddsd.ddpfPixelFormat_.dwRGBBitCount_ = 32;
1347     ddsd.ddpfPixelFormat_.dwRBitMask_ = 0x000000ff;
1348     ddsd.ddpfPixelFormat_.dwGBitMask_ = 0x0000ff00;
1349     ddsd.ddpfPixelFormat_.dwBBitMask_ = 0x00ff0000;
1350     ddsd.ddpfPixelFormat_.dwRGBAlphaBitMask_ = 0xff000000;
1351 
1352     outFile.Write(&ddsd, sizeof(ddsd));
1353     for (unsigned i = 0; i < levels.Size(); ++i)
1354         outFile.Write(levels[i]->GetData(), levels[i]->GetWidth() * levels[i]->GetHeight() * 4);
1355 
1356     return true;
1357 }
1358 
SaveWEBP(const String & fileName,float compression) const1359 bool Image::SaveWEBP(const String& fileName, float compression /* = 0.0f */) const
1360 {
1361 #ifdef URHO3D_WEBP
1362     URHO3D_PROFILE(SaveImageWEBP);
1363 
1364     FileSystem* fileSystem(GetSubsystem<FileSystem>());
1365     File outFile(context_, fileName, FILE_WRITE);
1366 
1367     if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
1368     {
1369         URHO3D_LOGERROR("Access denied to " + fileName);
1370         return false;
1371     }
1372 
1373     if (IsCompressed())
1374     {
1375         URHO3D_LOGERROR("Can not save compressed image to WebP");
1376         return false;
1377     }
1378 
1379     if (height_ > WEBP_MAX_DIMENSION || width_ > WEBP_MAX_DIMENSION)
1380     {
1381         URHO3D_LOGERROR("Maximum dimension supported by WebP is " + String(WEBP_MAX_DIMENSION));
1382         return false;
1383     }
1384 
1385     if (components_ != 4 && components_ != 3)
1386     {
1387         URHO3D_LOGERRORF("Can not save image with %u components to WebP, which requires 3 or 4; Try ConvertToRGBA first?", components_);
1388         return false;
1389     }
1390 
1391     if (!data_)
1392     {
1393         URHO3D_LOGERROR("No image data to save");
1394         return false;
1395     }
1396 
1397     WebPPicture pic;
1398     WebPConfig config;
1399     WebPMemoryWriter wrt;
1400     int importResult(0);
1401     size_t encodeResult(0);
1402 
1403     if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, compression) || !WebPPictureInit(&pic))
1404     {
1405         URHO3D_LOGERROR("WebP initialization failed; check installation");
1406         return false;
1407     }
1408     config.lossless = 1;
1409     config.exact = 1; // Preserve RGB values under transparency, as they may be wanted.
1410 
1411     pic.use_argb = 1;
1412     pic.width = width_;
1413     pic.height = height_;
1414     pic.writer = WebPMemoryWrite;
1415     pic.custom_ptr = &wrt;
1416     WebPMemoryWriterInit(&wrt);
1417 
1418     if (components_ == 4)
1419         importResult = WebPPictureImportRGBA(&pic, data_.Get(), components_ * width_);
1420     else if (components_ == 3)
1421         importResult = WebPPictureImportRGB(&pic, data_.Get(), components_ * width_);
1422 
1423     if (!importResult)
1424     {
1425         URHO3D_LOGERROR("WebP import of image data failed (truncated RGBA/RGB data or memory error?)");
1426         WebPPictureFree(&pic);
1427         WebPMemoryWriterClear(&wrt);
1428         return false;
1429     }
1430 
1431     encodeResult = WebPEncode(&config, &pic);
1432     // Check only general failure. WebPEncode() sets pic.error_code with specific error.
1433     if (!encodeResult)
1434     {
1435         URHO3D_LOGERRORF("WebP encoding failed (memory error?). WebPEncodingError = %d", pic.error_code);
1436         WebPPictureFree(&pic);
1437         WebPMemoryWriterClear(&wrt);
1438         return false;
1439     }
1440 
1441     WebPPictureFree(&pic);
1442     outFile.Write(wrt.mem, wrt.size);
1443     WebPMemoryWriterClear(&wrt);
1444 
1445     return true;
1446 #else
1447     URHO3D_LOGERROR("Cannot save in WEBP format, support not compiled in");
1448     return false;
1449 #endif
1450 }
1451 
1452 
GetPixel(int x,int y) const1453 Color Image::GetPixel(int x, int y) const
1454 {
1455     return GetPixel(x, y, 0);
1456 }
1457 
GetPixel(int x,int y,int z) const1458 Color Image::GetPixel(int x, int y, int z) const
1459 {
1460     if (!data_ || z < 0 || z >= depth_ || IsCompressed())
1461         return Color::BLACK;
1462     x = Clamp(x, 0, width_ - 1);
1463     y = Clamp(y, 0, height_ - 1);
1464 
1465     unsigned char* src = data_ + (z * width_ * height_ + y * width_ + x) * components_;
1466     Color ret;
1467 
1468     switch (components_)
1469     {
1470     case 4:
1471         ret.a_ = (float)src[3] / 255.0f;
1472         // Fall through
1473     case 3:
1474         ret.b_ = (float)src[2] / 255.0f;
1475         // Fall through
1476     case 2:
1477         ret.g_ = (float)src[1] / 255.0f;
1478         ret.r_ = (float)src[0] / 255.0f;
1479         break;
1480     default:
1481         ret.r_ = ret.g_ = ret.b_ = (float)src[0] / 255.0f;
1482         break;
1483     }
1484 
1485     return ret;
1486 }
1487 
GetPixelInt(int x,int y) const1488 unsigned Image::GetPixelInt(int x, int y) const
1489 {
1490     return GetPixelInt(x, y, 0);
1491 }
1492 
GetPixelInt(int x,int y,int z) const1493 unsigned Image::GetPixelInt(int x, int y, int z) const
1494 {
1495     if (!data_ || z < 0 || z >= depth_ || IsCompressed())
1496         return 0xff000000;
1497     x = Clamp(x, 0, width_ - 1);
1498     y = Clamp(y, 0, height_ - 1);
1499 
1500     unsigned char* src = data_ + (z * width_ * height_ + y * width_ + x) * components_;
1501     unsigned ret = 0;
1502     if (components_ < 4)
1503         ret |= 0xff000000;
1504 
1505     switch (components_)
1506     {
1507     case 4:
1508         ret |= (unsigned)src[3] << 24;
1509         // Fall through
1510     case 3:
1511         ret |= (unsigned)src[2] << 16;
1512         // Fall through
1513     case 2:
1514         ret |= (unsigned)src[1] << 8;
1515         ret |= (unsigned)src[0];
1516         break;
1517     default:
1518         ret |= (unsigned)src[0] << 16;
1519         ret |= (unsigned)src[0] << 8;
1520         ret |= (unsigned)src[0];
1521         break;
1522     }
1523 
1524     return ret;
1525 }
1526 
GetPixelBilinear(float x,float y) const1527 Color Image::GetPixelBilinear(float x, float y) const
1528 {
1529     x = Clamp(x * width_ - 0.5f, 0.0f, (float)(width_ - 1));
1530     y = Clamp(y * height_ - 0.5f, 0.0f, (float)(height_ - 1));
1531 
1532     int xI = (int)x;
1533     int yI = (int)y;
1534     float xF = Fract(x);
1535     float yF = Fract(y);
1536 
1537     Color topColor = GetPixel(xI, yI).Lerp(GetPixel(xI + 1, yI), xF);
1538     Color bottomColor = GetPixel(xI, yI + 1).Lerp(GetPixel(xI + 1, yI + 1), xF);
1539     return topColor.Lerp(bottomColor, yF);
1540 }
1541 
GetPixelTrilinear(float x,float y,float z) const1542 Color Image::GetPixelTrilinear(float x, float y, float z) const
1543 {
1544     if (depth_ < 2)
1545         return GetPixelBilinear(x, y);
1546 
1547     x = Clamp(x * width_ - 0.5f, 0.0f, (float)(width_ - 1));
1548     y = Clamp(y * height_ - 0.5f, 0.0f, (float)(height_ - 1));
1549     z = Clamp(z * depth_ - 0.5f, 0.0f, (float)(depth_ - 1));
1550 
1551     int xI = (int)x;
1552     int yI = (int)y;
1553     int zI = (int)z;
1554     if (zI == depth_ - 1)
1555         return GetPixelBilinear(x, y);
1556     float xF = Fract(x);
1557     float yF = Fract(y);
1558     float zF = Fract(z);
1559 
1560     Color topColorNear = GetPixel(xI, yI, zI).Lerp(GetPixel(xI + 1, yI, zI), xF);
1561     Color bottomColorNear = GetPixel(xI, yI + 1, zI).Lerp(GetPixel(xI + 1, yI + 1, zI), xF);
1562     Color colorNear = topColorNear.Lerp(bottomColorNear, yF);
1563     Color topColorFar = GetPixel(xI, yI, zI + 1).Lerp(GetPixel(xI + 1, yI, zI + 1), xF);
1564     Color bottomColorFar = GetPixel(xI, yI + 1, zI + 1).Lerp(GetPixel(xI + 1, yI + 1, zI + 1), xF);
1565     Color colorFar = topColorFar.Lerp(bottomColorFar, yF);
1566     return colorNear.Lerp(colorFar, zF);
1567 }
1568 
GetNextLevel() const1569 SharedPtr<Image> Image::GetNextLevel() const
1570 {
1571     if (IsCompressed())
1572     {
1573         URHO3D_LOGERROR("Can not generate mip level from compressed data");
1574         return SharedPtr<Image>();
1575     }
1576     if (components_ < 1 || components_ > 4)
1577     {
1578         URHO3D_LOGERROR("Illegal number of image components for mip level generation");
1579         return SharedPtr<Image>();
1580     }
1581 
1582     if (nextLevel_)
1583         return nextLevel_;
1584 
1585     URHO3D_PROFILE(CalculateImageMipLevel);
1586 
1587     int widthOut = width_ / 2;
1588     int heightOut = height_ / 2;
1589     int depthOut = depth_ / 2;
1590 
1591     if (widthOut < 1)
1592         widthOut = 1;
1593     if (heightOut < 1)
1594         heightOut = 1;
1595     if (depthOut < 1)
1596         depthOut = 1;
1597 
1598     SharedPtr<Image> mipImage(new Image(context_));
1599 
1600     if (depth_ > 1)
1601         mipImage->SetSize(widthOut, heightOut, depthOut, components_);
1602     else
1603         mipImage->SetSize(widthOut, heightOut, components_);
1604 
1605     const unsigned char* pixelDataIn = data_.Get();
1606     unsigned char* pixelDataOut = mipImage->data_.Get();
1607 
1608     // 1D case
1609     if (depth_ == 1 && (height_ == 1 || width_ == 1))
1610     {
1611         // Loop using the larger dimension
1612         if (widthOut < heightOut)
1613             widthOut = heightOut;
1614 
1615         switch (components_)
1616         {
1617         case 1:
1618             for (int x = 0; x < widthOut; ++x)
1619                 pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 1]) >> 1);
1620             break;
1621 
1622         case 2:
1623             for (int x = 0; x < widthOut * 2; x += 2)
1624             {
1625                 pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 2]) >> 1);
1626                 pixelDataOut[x + 1] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 1] + pixelDataIn[x * 2 + 3]) >> 1);
1627             }
1628             break;
1629 
1630         case 3:
1631             for (int x = 0; x < widthOut * 3; x += 3)
1632             {
1633                 pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 3]) >> 1);
1634                 pixelDataOut[x + 1] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 1] + pixelDataIn[x * 2 + 4]) >> 1);
1635                 pixelDataOut[x + 2] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 2] + pixelDataIn[x * 2 + 5]) >> 1);
1636             }
1637             break;
1638 
1639         case 4:
1640             for (int x = 0; x < widthOut * 4; x += 4)
1641             {
1642                 pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 4]) >> 1);
1643                 pixelDataOut[x + 1] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 1] + pixelDataIn[x * 2 + 5]) >> 1);
1644                 pixelDataOut[x + 2] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 2] + pixelDataIn[x * 2 + 6]) >> 1);
1645                 pixelDataOut[x + 3] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 3] + pixelDataIn[x * 2 + 7]) >> 1);
1646             }
1647             break;
1648 
1649         default:
1650             assert(false);  // Should never reach here
1651             break;
1652         }
1653     }
1654     // 2D case
1655     else if (depth_ == 1)
1656     {
1657         switch (components_)
1658         {
1659         case 1:
1660             for (int y = 0; y < heightOut; ++y)
1661             {
1662                 const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_];
1663                 const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_];
1664                 unsigned char* out = &pixelDataOut[y * widthOut];
1665 
1666                 for (int x = 0; x < widthOut; ++x)
1667                 {
1668                     out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 1] +
1669                                               inLower[x * 2] + inLower[x * 2 + 1]) >> 2);
1670                 }
1671             }
1672             break;
1673 
1674         case 2:
1675             for (int y = 0; y < heightOut; ++y)
1676             {
1677                 const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_ * 2];
1678                 const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_ * 2];
1679                 unsigned char* out = &pixelDataOut[y * widthOut * 2];
1680 
1681                 for (int x = 0; x < widthOut * 2; x += 2)
1682                 {
1683                     out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 2] +
1684                                               inLower[x * 2] + inLower[x * 2 + 2]) >> 2);
1685                     out[x + 1] = (unsigned char)(((unsigned)inUpper[x * 2 + 1] + inUpper[x * 2 + 3] +
1686                                                   inLower[x * 2 + 1] + inLower[x * 2 + 3]) >> 2);
1687                 }
1688             }
1689             break;
1690 
1691         case 3:
1692             for (int y = 0; y < heightOut; ++y)
1693             {
1694                 const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_ * 3];
1695                 const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_ * 3];
1696                 unsigned char* out = &pixelDataOut[y * widthOut * 3];
1697 
1698                 for (int x = 0; x < widthOut * 3; x += 3)
1699                 {
1700                     out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 3] +
1701                                               inLower[x * 2] + inLower[x * 2 + 3]) >> 2);
1702                     out[x + 1] = (unsigned char)(((unsigned)inUpper[x * 2 + 1] + inUpper[x * 2 + 4] +
1703                                                   inLower[x * 2 + 1] + inLower[x * 2 + 4]) >> 2);
1704                     out[x + 2] = (unsigned char)(((unsigned)inUpper[x * 2 + 2] + inUpper[x * 2 + 5] +
1705                                                   inLower[x * 2 + 2] + inLower[x * 2 + 5]) >> 2);
1706                 }
1707             }
1708             break;
1709 
1710         case 4:
1711             for (int y = 0; y < heightOut; ++y)
1712             {
1713                 const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_ * 4];
1714                 const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_ * 4];
1715                 unsigned char* out = &pixelDataOut[y * widthOut * 4];
1716 
1717                 for (int x = 0; x < widthOut * 4; x += 4)
1718                 {
1719                     out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 4] +
1720                                               inLower[x * 2] + inLower[x * 2 + 4]) >> 2);
1721                     out[x + 1] = (unsigned char)(((unsigned)inUpper[x * 2 + 1] + inUpper[x * 2 + 5] +
1722                                                   inLower[x * 2 + 1] + inLower[x * 2 + 5]) >> 2);
1723                     out[x + 2] = (unsigned char)(((unsigned)inUpper[x * 2 + 2] + inUpper[x * 2 + 6] +
1724                                                   inLower[x * 2 + 2] + inLower[x * 2 + 6]) >> 2);
1725                     out[x + 3] = (unsigned char)(((unsigned)inUpper[x * 2 + 3] + inUpper[x * 2 + 7] +
1726                                                   inLower[x * 2 + 3] + inLower[x * 2 + 7]) >> 2);
1727                 }
1728             }
1729             break;
1730 
1731         default:
1732             assert(false);  // Should never reach here
1733             break;
1734         }
1735     }
1736     // 3D case
1737     else
1738     {
1739         switch (components_)
1740         {
1741         case 1:
1742             for (int z = 0; z < depthOut; ++z)
1743             {
1744                 const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_];
1745                 const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_];
1746 
1747                 for (int y = 0; y < heightOut; ++y)
1748                 {
1749                     const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_];
1750                     const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_];
1751                     const unsigned char* inInnerUpper = &inInner[(y * 2) * width_];
1752                     const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_];
1753                     unsigned char* out = &pixelDataOut[z * widthOut * heightOut + y * widthOut];
1754 
1755                     for (int x = 0; x < widthOut; ++x)
1756                     {
1757                         out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 1] +
1758                                                   inOuterLower[x * 2] + inOuterLower[x * 2 + 1] +
1759                                                   inInnerUpper[x * 2] + inInnerUpper[x * 2 + 1] +
1760                                                   inInnerLower[x * 2] + inInnerLower[x * 2 + 1]) >> 3);
1761                     }
1762                 }
1763             }
1764             break;
1765 
1766         case 2:
1767             for (int z = 0; z < depthOut; ++z)
1768             {
1769                 const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_ * 2];
1770                 const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_ * 2];
1771 
1772                 for (int y = 0; y < heightOut; ++y)
1773                 {
1774                     const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_ * 2];
1775                     const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_ * 2];
1776                     const unsigned char* inInnerUpper = &inInner[(y * 2) * width_ * 2];
1777                     const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_ * 2];
1778                     unsigned char* out = &pixelDataOut[z * widthOut * heightOut * 2 + y * widthOut * 2];
1779 
1780                     for (int x = 0; x < widthOut * 2; x += 2)
1781                     {
1782                         out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 2] +
1783                                                   inOuterLower[x * 2] + inOuterLower[x * 2 + 2] +
1784                                                   inInnerUpper[x * 2] + inInnerUpper[x * 2 + 2] +
1785                                                   inInnerLower[x * 2] + inInnerLower[x * 2 + 2]) >> 3);
1786                         out[x + 1] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 1] + inOuterUpper[x * 2 + 3] +
1787                                                       inOuterLower[x * 2 + 1] + inOuterLower[x * 2 + 3] +
1788                                                       inInnerUpper[x * 2 + 1] + inInnerUpper[x * 2 + 3] +
1789                                                       inInnerLower[x * 2 + 1] + inInnerLower[x * 2 + 3]) >> 3);
1790                     }
1791                 }
1792             }
1793             break;
1794 
1795         case 3:
1796             for (int z = 0; z < depthOut; ++z)
1797             {
1798                 const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_ * 3];
1799                 const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_ * 3];
1800 
1801                 for (int y = 0; y < heightOut; ++y)
1802                 {
1803                     const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_ * 3];
1804                     const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_ * 3];
1805                     const unsigned char* inInnerUpper = &inInner[(y * 2) * width_ * 3];
1806                     const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_ * 3];
1807                     unsigned char* out = &pixelDataOut[z * widthOut * heightOut * 3 + y * widthOut * 3];
1808 
1809                     for (int x = 0; x < widthOut * 3; x += 3)
1810                     {
1811                         out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 3] +
1812                                                   inOuterLower[x * 2] + inOuterLower[x * 2 + 3] +
1813                                                   inInnerUpper[x * 2] + inInnerUpper[x * 2 + 3] +
1814                                                   inInnerLower[x * 2] + inInnerLower[x * 2 + 3]) >> 3);
1815                         out[x + 1] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 1] + inOuterUpper[x * 2 + 4] +
1816                                                       inOuterLower[x * 2 + 1] + inOuterLower[x * 2 + 4] +
1817                                                       inInnerUpper[x * 2 + 1] + inInnerUpper[x * 2 + 4] +
1818                                                       inInnerLower[x * 2 + 1] + inInnerLower[x * 2 + 4]) >> 3);
1819                         out[x + 2] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 2] + inOuterUpper[x * 2 + 5] +
1820                                                       inOuterLower[x * 2 + 2] + inOuterLower[x * 2 + 5] +
1821                                                       inInnerUpper[x * 2 + 2] + inInnerUpper[x * 2 + 5] +
1822                                                       inInnerLower[x * 2 + 2] + inInnerLower[x * 2 + 5]) >> 3);
1823                     }
1824                 }
1825             }
1826             break;
1827 
1828         case 4:
1829             for (int z = 0; z < depthOut; ++z)
1830             {
1831                 const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_ * 4];
1832                 const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_ * 4];
1833 
1834                 for (int y = 0; y < heightOut; ++y)
1835                 {
1836                     const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_ * 4];
1837                     const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_ * 4];
1838                     const unsigned char* inInnerUpper = &inInner[(y * 2) * width_ * 4];
1839                     const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_ * 4];
1840                     unsigned char* out = &pixelDataOut[z * widthOut * heightOut * 4 + y * widthOut * 4];
1841 
1842                     for (int x = 0; x < widthOut * 4; x += 4)
1843                     {
1844                         out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 4] +
1845                                                   inOuterLower[x * 2] + inOuterLower[x * 2 + 4] +
1846                                                   inInnerUpper[x * 2] + inInnerUpper[x * 2 + 4] +
1847                                                   inInnerLower[x * 2] + inInnerLower[x * 2 + 4]) >> 3);
1848                         out[x + 1] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 1] + inOuterUpper[x * 2 + 5] +
1849                                                       inOuterLower[x * 2 + 1] + inOuterLower[x * 2 + 5] +
1850                                                       inInnerUpper[x * 2 + 1] + inInnerUpper[x * 2 + 5] +
1851                                                       inInnerLower[x * 2 + 1] + inInnerLower[x * 2 + 5]) >> 3);
1852                         out[x + 2] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 2] + inOuterUpper[x * 2 + 6] +
1853                                                       inOuterLower[x * 2 + 2] + inOuterLower[x * 2 + 6] +
1854                                                       inInnerUpper[x * 2 + 2] + inInnerUpper[x * 2 + 6] +
1855                                                       inInnerLower[x * 2 + 2] + inInnerLower[x * 2 + 6]) >> 3);
1856                     }
1857                 }
1858             }
1859             break;
1860 
1861         default:
1862             assert(false);  // Should never reach here
1863             break;
1864         }
1865     }
1866 
1867     return mipImage;
1868 }
1869 
ConvertToRGBA() const1870 SharedPtr<Image> Image::ConvertToRGBA() const
1871 {
1872     if (IsCompressed())
1873     {
1874         URHO3D_LOGERROR("Can not convert compressed image to RGBA");
1875         return SharedPtr<Image>();
1876     }
1877     if (components_ < 1 || components_ > 4)
1878     {
1879         URHO3D_LOGERROR("Illegal number of image components for conversion to RGBA");
1880         return SharedPtr<Image>();
1881     }
1882     if (!data_)
1883     {
1884         URHO3D_LOGERROR("Can not convert image without data to RGBA");
1885         return SharedPtr<Image>();
1886     }
1887 
1888     // Already RGBA?
1889     if (components_ == 4)
1890         return SharedPtr<Image>(const_cast<Image*>(this));
1891 
1892     SharedPtr<Image> ret(new Image(context_));
1893     ret->SetSize(width_, height_, depth_, 4);
1894 
1895     const unsigned char* src = data_;
1896     unsigned char* dest = ret->GetData();
1897 
1898     switch (components_)
1899     {
1900     case 1:
1901         for (unsigned i = 0; i < static_cast<unsigned>(width_ * height_ * depth_); ++i)
1902         {
1903             unsigned char pixel = *src++;
1904             *dest++ = pixel;
1905             *dest++ = pixel;
1906             *dest++ = pixel;
1907             *dest++ = 255;
1908         }
1909         break;
1910 
1911     case 2:
1912         for (unsigned i = 0; i < static_cast<unsigned>(width_ * height_ * depth_); ++i)
1913         {
1914             unsigned char pixel = *src++;
1915             *dest++ = pixel;
1916             *dest++ = pixel;
1917             *dest++ = pixel;
1918             *dest++ = *src++;
1919         }
1920         break;
1921 
1922     case 3:
1923         for (unsigned i = 0; i < static_cast<unsigned>(width_ * height_ * depth_); ++i)
1924         {
1925             *dest++ = *src++;
1926             *dest++ = *src++;
1927             *dest++ = *src++;
1928             *dest++ = 255;
1929         }
1930         break;
1931 
1932     default:
1933         assert(false);  // Should never reach nere
1934         break;
1935     }
1936 
1937     return ret;
1938 }
1939 
GetCompressedLevel(unsigned index) const1940 CompressedLevel Image::GetCompressedLevel(unsigned index) const
1941 {
1942     CompressedLevel level;
1943 
1944     if (compressedFormat_ == CF_NONE)
1945     {
1946         URHO3D_LOGERROR("Image is not compressed");
1947         return level;
1948     }
1949     if (index >= numCompressedLevels_)
1950     {
1951         URHO3D_LOGERROR("Compressed image mip level out of bounds");
1952         return level;
1953     }
1954 
1955     level.format_ = compressedFormat_;
1956     level.width_ = width_;
1957     level.height_ = height_;
1958     level.depth_ = depth_;
1959 
1960     if (compressedFormat_ == CF_RGBA)
1961     {
1962         level.blockSize_ = 4;
1963         unsigned i = 0;
1964         unsigned offset = 0;
1965 
1966         for (;;)
1967         {
1968             if (!level.width_)
1969                 level.width_ = 1;
1970             if (!level.height_)
1971                 level.height_ = 1;
1972             if (!level.depth_)
1973                 level.depth_ = 1;
1974 
1975             level.rowSize_ = level.width_ * level.blockSize_;
1976             level.rows_ = (unsigned)level.height_;
1977             level.data_ = data_.Get() + offset;
1978             level.dataSize_ = level.depth_ * level.rows_ * level.rowSize_;
1979 
1980             if (offset + level.dataSize_ > GetMemoryUse())
1981             {
1982                 URHO3D_LOGERROR("Compressed level is outside image data. Offset: " + String(offset) + " Size: " + String(level.dataSize_) +
1983                          " Datasize: " + String(GetMemoryUse()));
1984                 level.data_ = 0;
1985                 return level;
1986             }
1987 
1988             if (i == index)
1989                 return level;
1990 
1991             offset += level.dataSize_;
1992             level.width_ /= 2;
1993             level.height_ /= 2;
1994             level.depth_ /= 2;
1995             ++i;
1996         }
1997     }
1998     else if (compressedFormat_ < CF_PVRTC_RGB_2BPP)
1999     {
2000         level.blockSize_ = (compressedFormat_ == CF_DXT1 || compressedFormat_ == CF_ETC1) ? 8 : 16;
2001         unsigned i = 0;
2002         unsigned offset = 0;
2003 
2004         for (;;)
2005         {
2006             if (!level.width_)
2007                 level.width_ = 1;
2008             if (!level.height_)
2009                 level.height_ = 1;
2010             if (!level.depth_)
2011                 level.depth_ = 1;
2012 
2013             level.rowSize_ = ((level.width_ + 3) / 4) * level.blockSize_;
2014             level.rows_ = (unsigned)((level.height_ + 3) / 4);
2015             level.data_ = data_.Get() + offset;
2016             level.dataSize_ = level.depth_ * level.rows_ * level.rowSize_;
2017 
2018             if (offset + level.dataSize_ > GetMemoryUse())
2019             {
2020                 URHO3D_LOGERROR("Compressed level is outside image data. Offset: " + String(offset) + " Size: " + String(level.dataSize_) +
2021                          " Datasize: " + String(GetMemoryUse()));
2022                 level.data_ = 0;
2023                 return level;
2024             }
2025 
2026             if (i == index)
2027                 return level;
2028 
2029             offset += level.dataSize_;
2030             level.width_ /= 2;
2031             level.height_ /= 2;
2032             level.depth_ /= 2;
2033             ++i;
2034         }
2035     }
2036     else
2037     {
2038         level.blockSize_ = compressedFormat_ < CF_PVRTC_RGB_4BPP ? 2 : 4;
2039         unsigned i = 0;
2040         unsigned offset = 0;
2041 
2042         for (;;)
2043         {
2044             if (!level.width_)
2045                 level.width_ = 1;
2046             if (!level.height_)
2047                 level.height_ = 1;
2048 
2049             int dataWidth = Max(level.width_, level.blockSize_ == 2 ? 16 : 8);
2050             int dataHeight = Max(level.height_, 8);
2051             level.data_ = data_.Get() + offset;
2052             level.dataSize_ = (dataWidth * dataHeight * level.blockSize_ + 7) >> 3;
2053             level.rows_ = (unsigned)dataHeight;
2054             level.rowSize_ = level.dataSize_ / level.rows_;
2055 
2056             if (offset + level.dataSize_ > GetMemoryUse())
2057             {
2058                 URHO3D_LOGERROR("Compressed level is outside image data. Offset: " + String(offset) + " Size: " + String(level.dataSize_) +
2059                          " Datasize: " + String(GetMemoryUse()));
2060                 level.data_ = 0;
2061                 return level;
2062             }
2063 
2064             if (i == index)
2065                 return level;
2066 
2067             offset += level.dataSize_;
2068             level.width_ /= 2;
2069             level.height_ /= 2;
2070             ++i;
2071         }
2072     }
2073 }
2074 
GetSubimage(const IntRect & rect) const2075 Image* Image::GetSubimage(const IntRect& rect) const
2076 {
2077     if (!data_)
2078         return 0;
2079 
2080     if (depth_ > 1)
2081     {
2082         URHO3D_LOGERROR("Subimage not supported for 3D images");
2083         return 0;
2084     }
2085 
2086     if (rect.left_ < 0 || rect.top_ < 0 || rect.right_ > width_ || rect.bottom_ > height_ || !rect.Width() || !rect.Height())
2087     {
2088         URHO3D_LOGERROR("Can not get subimage from image " + GetName() + " with invalid region");
2089         return 0;
2090     }
2091 
2092     if (!IsCompressed())
2093     {
2094         int x = rect.left_;
2095         int y = rect.top_;
2096         int width = rect.Width();
2097         int height = rect.Height();
2098 
2099         Image* image = new Image(context_);
2100         image->SetSize(width, height, components_);
2101 
2102         unsigned char* dest = image->GetData();
2103         unsigned char* src = data_.Get() + (y * width_ + x) * components_;
2104         for (int i = 0; i < height; ++i)
2105         {
2106             memcpy(dest, src, width * components_);
2107             dest += width * components_;
2108             src += width_ * components_;
2109         }
2110 
2111         return image;
2112     }
2113     else
2114     {
2115         // Pad the region to be a multiple of block size
2116         IntRect paddedRect = rect;
2117         paddedRect.left_ = (rect.left_ / 4) * 4;
2118         paddedRect.top_ = (rect.top_ / 4) * 4;
2119         paddedRect.right_ = (rect.right_ / 4) * 4;
2120         paddedRect.bottom_ = (rect.bottom_ / 4) * 4;
2121         IntRect currentRect = paddedRect;
2122 
2123         PODVector<unsigned char> subimageData;
2124         unsigned subimageLevels = 0;
2125 
2126         // Save as many mips as possible until the next mip would cross a block boundary
2127         for (unsigned i = 0; i < numCompressedLevels_; ++i)
2128         {
2129             CompressedLevel level = GetCompressedLevel(i);
2130             if (!level.data_)
2131                 break;
2132 
2133             // Mips are stored continuously
2134             unsigned destStartOffset = subimageData.Size();
2135             unsigned destRowSize = currentRect.Width() / 4 * level.blockSize_;
2136             unsigned destSize = currentRect.Height() / 4 * destRowSize;
2137             if (!destSize)
2138                 break;
2139 
2140             subimageData.Resize(destStartOffset + destSize);
2141             unsigned char* dest = &subimageData[destStartOffset];
2142 
2143             for (int y = currentRect.top_; y < currentRect.bottom_; y += 4)
2144             {
2145                 unsigned char* src = level.data_ + level.rowSize_ * (y / 4) + currentRect.left_ / 4 * level.blockSize_;
2146                 memcpy(dest, src, destRowSize);
2147                 dest += destRowSize;
2148             }
2149 
2150             ++subimageLevels;
2151             if ((currentRect.left_ & 4) || (currentRect.right_ & 4) || (currentRect.top_ & 4) || (currentRect.bottom_ & 4))
2152                 break;
2153             else
2154             {
2155                 currentRect.left_ /= 2;
2156                 currentRect.right_ /= 2;
2157                 currentRect.top_ /= 2;
2158                 currentRect.bottom_ /= 2;
2159             }
2160         }
2161 
2162         if (!subimageLevels)
2163         {
2164             URHO3D_LOGERROR("Subimage region from compressed image " + GetName() + " did not produce any data");
2165             return 0;
2166         }
2167 
2168         Image* image = new Image(context_);
2169         image->width_ = paddedRect.Width();
2170         image->height_ = paddedRect.Height();
2171         image->depth_ = 1;
2172         image->compressedFormat_ = compressedFormat_;
2173         image->numCompressedLevels_ = subimageLevels;
2174         image->components_ = components_;
2175         image->data_ = new unsigned char[subimageData.Size()];
2176         memcpy(image->data_.Get(), &subimageData[0], subimageData.Size());
2177         image->SetMemoryUse(subimageData.Size());
2178 
2179         return image;
2180     }
2181 }
2182 
GetSDLSurface(const IntRect & rect) const2183 SDL_Surface* Image::GetSDLSurface(const IntRect& rect) const
2184 {
2185     if (!data_)
2186         return 0;
2187 
2188     if (depth_ > 1)
2189     {
2190         URHO3D_LOGERROR("Can not get SDL surface from 3D image");
2191         return 0;
2192     }
2193 
2194     if (IsCompressed())
2195     {
2196         URHO3D_LOGERROR("Can not get SDL surface from compressed image " + GetName());
2197         return 0;
2198     }
2199 
2200     if (components_ < 3)
2201     {
2202         URHO3D_LOGERROR("Can not get SDL surface from image " + GetName() + " with less than 3 components");
2203         return 0;
2204     }
2205 
2206     IntRect imageRect = rect;
2207     // Use full image if illegal rect
2208     if (imageRect.left_ < 0 || imageRect.top_ < 0 || imageRect.right_ > width_ || imageRect.bottom_ > height_ ||
2209         imageRect.left_ >= imageRect.right_ || imageRect.top_ >= imageRect.bottom_)
2210     {
2211         imageRect.left_ = 0;
2212         imageRect.top_ = 0;
2213         imageRect.right_ = width_;
2214         imageRect.bottom_ = height_;
2215     }
2216 
2217     int imageWidth = width_;
2218     int width = imageRect.Width();
2219     int height = imageRect.Height();
2220 
2221     // Assume little-endian for all the supported platforms
2222     unsigned rMask = 0x000000ff;
2223     unsigned gMask = 0x0000ff00;
2224     unsigned bMask = 0x00ff0000;
2225     unsigned aMask = 0xff000000;
2226 
2227     SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, components_ * 8, rMask, gMask, bMask, aMask);
2228     if (surface)
2229     {
2230         SDL_LockSurface(surface);
2231 
2232         unsigned char* destination = reinterpret_cast<unsigned char*>(surface->pixels);
2233         unsigned char* source = data_ + components_ * (imageWidth * imageRect.top_ + imageRect.left_);
2234         for (int i = 0; i < height; ++i)
2235         {
2236             memcpy(destination, source, components_ * width);
2237             destination += surface->pitch;
2238             source += components_ * imageWidth;
2239         }
2240 
2241         SDL_UnlockSurface(surface);
2242     }
2243     else
2244         URHO3D_LOGERROR("Failed to create SDL surface from image " + GetName());
2245 
2246     return surface;
2247 }
2248 
PrecalculateLevels()2249 void Image::PrecalculateLevels()
2250 {
2251     if (!data_ || IsCompressed())
2252         return;
2253 
2254     URHO3D_PROFILE(PrecalculateImageMipLevels);
2255 
2256     nextLevel_.Reset();
2257 
2258     if (width_ > 1 || height_ > 1)
2259     {
2260         SharedPtr<Image> current = GetNextLevel();
2261         nextLevel_ = current;
2262         while (current && (current->width_ > 1 || current->height_ > 1))
2263         {
2264             current->nextLevel_ = current->GetNextLevel();
2265             current = current->nextLevel_;
2266         }
2267     }
2268 }
2269 
CleanupLevels()2270 void Image::CleanupLevels()
2271 {
2272     nextLevel_.Reset();
2273 }
2274 
GetLevels(PODVector<Image * > & levels)2275 void Image::GetLevels(PODVector<Image*>& levels)
2276 {
2277     levels.Clear();
2278 
2279     Image* image = this;
2280     while (image)
2281     {
2282         levels.Push(image);
2283         image = image->nextLevel_;
2284     }
2285 }
2286 
GetLevels(PODVector<const Image * > & levels) const2287 void Image::GetLevels(PODVector<const Image*>& levels) const
2288 {
2289     levels.Clear();
2290 
2291     const Image* image = this;
2292     while (image)
2293     {
2294         levels.Push(image);
2295         image = image->nextLevel_;
2296     }
2297 }
2298 
GetImageData(Deserializer & source,int & width,int & height,unsigned & components)2299 unsigned char* Image::GetImageData(Deserializer& source, int& width, int& height, unsigned& components)
2300 {
2301     unsigned dataSize = source.GetSize();
2302 
2303     SharedArrayPtr<unsigned char> buffer(new unsigned char[dataSize]);
2304     source.Read(buffer.Get(), dataSize);
2305     return stbi_load_from_memory(buffer.Get(), dataSize, &width, &height, (int*)&components, 0);
2306 }
2307 
FreeImageData(unsigned char * pixelData)2308 void Image::FreeImageData(unsigned char* pixelData)
2309 {
2310     if (!pixelData)
2311         return;
2312 
2313     stbi_image_free(pixelData);
2314 }
2315 
2316 }
2317