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