1 /** @file image.cpp Image objects and related routines.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * GPL: http://www.gnu.org/licenses/gpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details. You should have received a copy of the GNU
16 * General Public License along with this program; if not, write to the Free
17 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18 * 02110-1301 USA</small>
19 */
20
21 #include "de_platform.h"
22 #include "resource/image.h"
23 #include "resource/tga.h"
24
25 #include <doomsday/resource/patch.h>
26 #include <doomsday/resource/colorpalettes.h>
27
28 #include <de/memory.h>
29 #include <de/LogBuffer>
30 #include <QByteArray>
31 #include <QImage>
32 #include <de/NativePath>
33 #include <doomsday/filesys/fs_main.h>
34
35 #include "dd_main.h"
36
37 #include <doomsday/res/Composite>
38 #include <doomsday/resource/pcx.h>
39
40 #include "gl/gl_tex.h"
41
42 #include "render/rend_main.h" // misc global vars awaiting new home
43
44 #ifndef DENG2_QT_4_7_OR_NEWER // older than 4.7?
45 # define constBits bits
46 #endif
47
48 using namespace de;
49 using namespace res;
50
51 struct GraphicFileType
52 {
53 /// Symbolic name of the resource type.
54 String name;
55
56 /// Known file extension.
57 String ext;
58
59 bool (*interpretFunc)(FileHandle &hndl, String filePath, image_t &img);
60
61 //char const *(*getLastErrorFunc)(); ///< Can be NULL.
62 };
63
interpretPcx(FileHandle & hndl,String,image_t & img)64 static bool interpretPcx(FileHandle &hndl, String /*filePath*/, image_t &img)
65 {
66 Image_Init(img);
67 img.pixels = PCX_Load(hndl, img.size, img.pixelSize);
68 return (0 != img.pixels);
69 }
70
interpretJpg(FileHandle & hndl,String,image_t & img)71 static bool interpretJpg(FileHandle &hndl, String /*filePath*/, image_t &img)
72 {
73 return Image_LoadFromFileWithFormat(img, "JPG", hndl);
74 }
75
interpretPng(FileHandle & hndl,String,image_t & img)76 static bool interpretPng(FileHandle &hndl, String /*filePath*/, image_t &img)
77 {
78 return Image_LoadFromFileWithFormat(img, "PNG", hndl);
79 }
80
interpretTga(FileHandle & hndl,String,image_t & img)81 static bool interpretTga(FileHandle &hndl, String /*filePath*/, image_t &img)
82 {
83 Image_Init(img);
84 img.pixels = TGA_Load(hndl, img.size, img.pixelSize);
85 return img.pixels != nullptr;
86 }
87
88 // Graphic resource types.
89 static GraphicFileType const graphicTypes[] = {
90 { "PNG", "png", interpretPng /*, 0*/ },
91 { "JPG", "jpg", interpretJpg /*, 0*/ }, // TODO: add alternate "jpeg" extension
92 { "TGA", "tga", interpretTga /*, TGA_LastError*/ },
93 { "PCX", "pcx", interpretPcx /*, PCX_LastError*/ },
94 { "", "", nullptr /*, 0*/ } // Terminate.
95 };
96
guessGraphicFileTypeFromFileName(String fileName)97 static GraphicFileType const *guessGraphicFileTypeFromFileName(String fileName)
98 {
99 // The path must have an extension for this.
100 String ext = fileName.fileNameExtension();
101 if (!ext.isEmpty())
102 {
103 for (int i = 0; !graphicTypes[i].ext.isEmpty(); ++i)
104 {
105 GraphicFileType const &type = graphicTypes[i];
106 if (!ext.compareWithoutCase(type.ext))
107 {
108 return &type;
109 }
110 }
111 }
112 return nullptr; // Unknown.
113 }
114
interpretGraphic(FileHandle & hndl,String filePath,image_t & img)115 static void interpretGraphic(FileHandle &hndl, String filePath, image_t &img)
116 {
117 // Firstly try the interpreter for the guessed resource types.
118 GraphicFileType const *rtypeGuess = guessGraphicFileTypeFromFileName(filePath);
119 if (rtypeGuess)
120 {
121 rtypeGuess->interpretFunc(hndl, filePath, img);
122 }
123
124 // If not yet interpreted - try each recognisable format in order.
125 if (!img.pixels)
126 {
127 // Try each recognisable format instead.
128 /// @todo Order here should be determined by the resource locator.
129 for (int i = 0; !graphicTypes[i].name.isEmpty(); ++i)
130 {
131 GraphicFileType const *graphicType = &graphicTypes[i];
132
133 // Already tried this?
134 if (graphicType == rtypeGuess) continue;
135
136 graphicTypes[i].interpretFunc(hndl, filePath, img);
137 if (img.pixels) break;
138 }
139 }
140 }
141
142 /// @return @c true if the file name in @a path ends with the "color key" suffix.
isColorKeyed(String path)143 static inline bool isColorKeyed(String path)
144 {
145 return path.fileNameWithoutExtension().endsWith("-ck", String::CaseInsensitive);
146 }
147
Image_Init(image_t & img)148 void Image_Init(image_t &img)
149 {
150 img.size = Vector2ui(0, 0);
151 img.pixelSize = 0;
152 img.flags = 0;
153 img.paletteId = 0;
154 img.pixels = 0;
155 }
156
Image_InitFromImage(image_t & img,Image const & guiImage)157 void Image_InitFromImage(image_t &img, Image const &guiImage)
158 {
159 img.size = guiImage.size();
160 img.pixelSize = guiImage.depth() / 8;
161 img.flags = 0;
162 img.paletteId = 0;
163 img.pixels = reinterpret_cast<uint8_t *>(M_Malloc(guiImage.byteCount()));
164 std::memcpy(img.pixels, guiImage.bits(), guiImage.byteCount());
165 }
166
Image_ClearPixelData(image_t & img)167 void Image_ClearPixelData(image_t &img)
168 {
169 M_Free(img.pixels); img.pixels = 0;
170 }
171
Image_Size(image_t const & img)172 image_t::Size Image_Size(image_t const &img)
173 {
174 return img.size;
175 }
176
Image_Description(image_t const & img)177 String Image_Description(image_t const &img)
178 {
179 return String("Dimensions:%1 Flags:%2 %3:%4")
180 .arg(img.size.asText())
181 .arg(img.flags)
182 .arg(0 != img.paletteId? "ColorPalette" : "PixelSize")
183 .arg(0 != img.paletteId? img.paletteId : img.pixelSize);
184 }
185
Image_ConvertToLuminance(image_t & img,bool retainAlpha)186 void Image_ConvertToLuminance(image_t &img, bool retainAlpha)
187 {
188 LOG_AS("Image_ConvertToLuminance");
189
190 uint8_t *alphaChannel = 0, *ptr = 0;
191
192 // Is this suitable?
193 if (0 != img.paletteId || (img.pixelSize < 3 && (img.flags & IMGF_IS_MASKED)))
194 {
195 LOG_RES_WARNING("Unknown paletted/masked image format");
196 return;
197 }
198
199 long numPels = img.size.x * img.size.y;
200
201 // Do we need to relocate the alpha data?
202 if (retainAlpha && img.pixelSize == 4)
203 {
204 // Yes. Take a copy.
205 alphaChannel = reinterpret_cast<uint8_t *>(M_Malloc(numPels));
206
207 ptr = img.pixels;
208 for (long p = 0; p < numPels; ++p, ptr += img.pixelSize)
209 {
210 alphaChannel[p] = ptr[3];
211 }
212 }
213
214 // Average the RGB colors.
215 ptr = img.pixels;
216 for (long p = 0; p < numPels; ++p, ptr += img.pixelSize)
217 {
218 int min = de::min(ptr[0], de::min(ptr[1], ptr[2]));
219 int max = de::max(ptr[0], de::max(ptr[1], ptr[2]));
220 img.pixels[p] = (min == max? min : (min + max) / 2);
221 }
222
223 // Do we need to relocate the alpha data?
224 if (alphaChannel)
225 {
226 std::memcpy(img.pixels + numPels, alphaChannel, numPels);
227 img.pixelSize = 2;
228 M_Free(alphaChannel);
229 return;
230 }
231
232 img.pixelSize = 1;
233 }
234
Image_ConvertToAlpha(image_t & img,bool makeWhite)235 void Image_ConvertToAlpha(image_t &img, bool makeWhite)
236 {
237 Image_ConvertToLuminance(img);
238
239 long total = img.size.x * img.size.y;
240 for (long p = 0; p < total; ++p)
241 {
242 img.pixels[total + p] = img.pixels[p];
243 if (makeWhite) img.pixels[p] = 255;
244 }
245 img.pixelSize = 2;
246 }
247
Image_HasAlpha(image_t const & img)248 bool Image_HasAlpha(image_t const &img)
249 {
250 LOG_AS("Image_HasAlpha");
251
252 if (0 != img.paletteId || (img.flags & IMGF_IS_MASKED))
253 {
254 LOG_RES_WARNING("Unknown paletted/masked image format");
255 return false;
256 }
257
258 if (img.pixelSize == 3)
259 {
260 return false;
261 }
262
263 if (img.pixelSize == 4)
264 {
265 long const numpels = img.size.x * img.size.y;
266 uint8_t const *in = img.pixels;
267 for (long i = 0; i < numpels; ++i, in += 4)
268 {
269 if (in[3] < 255)
270 {
271 return true;
272 }
273 }
274 }
275
276 return false;
277 }
278
Image_LoadFromFile(image_t & img,FileHandle & file)279 uint8_t *Image_LoadFromFile(image_t &img, FileHandle &file)
280 {
281 LOG_AS("Image_LoadFromFile");
282
283 String filePath = file.file().composePath();
284
285 Image_Init(img);
286 interpretGraphic(file, filePath, img);
287
288 // Still not interpreted?
289 if (!img.pixels)
290 {
291 LOG_RES_XVERBOSE("\"%s\" unrecognized, trying fallback loader...",
292 NativePath(filePath).pretty());
293 return 0; // Not a recognised format. It may still be loadable, however.
294 }
295
296 // How about some color-keying?
297 if (isColorKeyed(filePath))
298 {
299 uint8_t *out = ApplyColorKeying(img.pixels, img.size.x, img.size.y, img.pixelSize);
300 if (out != img.pixels)
301 {
302 // Had to allocate a larger buffer, free the old and attach the new.
303 M_Free(img.pixels);
304 img.pixels = out;
305 }
306
307 // Color keying is done; now we have 4 bytes per pixel.
308 img.pixelSize = 4;
309 }
310
311 // Any alpha pixels?
312 if (Image_HasAlpha(img))
313 {
314 img.flags |= IMGF_IS_MASKED;
315 }
316
317 LOG_RES_VERBOSE("Loaded image from file \"%s\", size %s")
318 << NativePath(filePath).pretty() << img.size.asText();
319
320 return img.pixels;
321 }
322
Image_LoadFromFileWithFormat(image_t & img,char const * format,FileHandle & hndl)323 bool Image_LoadFromFileWithFormat(image_t &img, char const *format, FileHandle &hndl)
324 {
325 LOG_AS("Image_LoadFromFileWithFormat");
326
327 /// @todo There are too many copies made here. It would be best if image_t
328 /// contained an instance of QImage. -jk
329
330 // It is assumed that file's position stays the same (could be trying multiple interpreters).
331 size_t initPos = hndl.tell();
332
333 Image_Init(img);
334
335 // Load the file contents to a memory buffer.
336 QByteArray data;
337 data.resize(hndl.length() - initPos);
338 hndl.read(reinterpret_cast<uint8_t*>(data.data()), data.size());
339
340 QImage image = QImage::fromData(data, format);
341 if (image.isNull())
342 {
343 // Back to the original file position.
344 hndl.seek(initPos, SeekSet);
345 return false;
346 }
347
348 //LOG_TRACE("Loading \"%s\"...") << NativePath(hndl->file().composePath()).pretty();
349
350 // Convert paletted images to RGB.
351 if (image.colorCount())
352 {
353 image = image.convertToFormat(QImage::Format_ARGB32);
354 DENG_ASSERT(!image.colorCount());
355 DENG_ASSERT(image.depth() == 32);
356 }
357
358 // Swap the red and blue channels for GL.
359 image = image.rgbSwapped();
360
361 img.size = Vector2ui(image.width(), image.height());
362 img.pixelSize = image.depth() / 8;
363
364 LOGDEV_RES_VERBOSE("size:%s depth:%i alpha:%b bytes:%i")
365 << img.size.asText() << img.pixelSize
366 << image.hasAlphaChannel() << image.byteCount();
367
368 img.pixels = reinterpret_cast<uint8_t *>(M_MemDup(image.constBits(), image.byteCount()));
369
370 // Back to the original file position.
371 hndl.seek(initPos, SeekSet);
372 return true;
373 }
374
Image_Save(image_t const & img,char const * filePath)375 bool Image_Save(image_t const &img, char const *filePath)
376 {
377 // Compose the full path.
378 String fullPath = String(filePath);
379 if (fullPath.isEmpty())
380 {
381 static int n = 0;
382 fullPath = String("image%1x%2-%3").arg(img.size.x).arg(img.size.y).arg(n++, 3);
383 }
384
385 if (fullPath.fileNameExtension().isEmpty())
386 {
387 fullPath += ".png";
388 }
389
390 // Swap red and blue channels then save.
391 QImage image = QImage(img.pixels, img.size.x, img.size.y, QImage::Format_ARGB32);
392 image = image.rgbSwapped();
393
394 return image.save(NativePath(fullPath));
395 }
396
GL_LoadImage(image_t & image,String nativePath)397 uint8_t *GL_LoadImage(image_t &image, String nativePath)
398 {
399 try
400 {
401 // Relative paths are relative to the native working directory.
402 String path = (NativePath::workPath() / NativePath(nativePath).expand()).withSeparators('/');
403
404 FileHandle &hndl = App_FileSystem().openFile(path, "rb");
405 uint8_t *pixels = Image_LoadFromFile(image, hndl);
406
407 App_FileSystem().releaseFile(hndl.file());
408 delete &hndl;
409
410 return pixels;
411 }
412 catch (FS1::NotFoundError const&)
413 {} // Ignore error.
414
415 return 0; // Not loaded.
416 }
417
GL_LoadExtImage(image_t & image,char const * _searchPath,gfxmode_t mode)418 Source GL_LoadExtImage(image_t &image, char const *_searchPath, gfxmode_t mode)
419 {
420 DENG_ASSERT(_searchPath);
421
422 try
423 {
424 String foundPath = App_FileSystem().findPath(de::Uri(RC_GRAPHIC, _searchPath),
425 RLF_DEFAULT, App_ResourceClass(RC_GRAPHIC));
426 // Ensure the found path is absolute.
427 foundPath = App_BasePath() / foundPath;
428
429 if (GL_LoadImage(image, foundPath))
430 {
431 // Force it to grayscale?
432 if (mode == LGM_GRAYSCALE_ALPHA || mode == LGM_WHITE_ALPHA)
433 {
434 Image_ConvertToAlpha(image, mode == LGM_WHITE_ALPHA);
435 }
436 else if (mode == LGM_GRAYSCALE)
437 {
438 Image_ConvertToLuminance(image);
439 }
440
441 return External;
442 }
443 }
444 catch (FS1::NotFoundError const&)
445 {} // Ignore this error.
446
447 return None;
448 }
449
palettedIsMasked(uint8_t const * pixels,int width,int height)450 static dd_bool palettedIsMasked(uint8_t const *pixels, int width, int height)
451 {
452 DENG2_ASSERT(pixels != 0);
453 // Jump to the start of the alpha data.
454 pixels += width * height;
455 for (int i = 0; i < width * height; ++i)
456 {
457 if (255 != pixels[i])
458 {
459 return true;
460 }
461 }
462 return false;
463 }
464
loadExternalTexture(image_t & image,String encodedSearchPath,String optionalSuffix="")465 static Source loadExternalTexture(image_t &image, String encodedSearchPath,
466 String optionalSuffix = "")
467 {
468 // First look for a version with an optional suffix.
469 try
470 {
471 String foundPath = App_FileSystem().findPath(de::Uri(encodedSearchPath + optionalSuffix, RC_GRAPHIC),
472 RLF_DEFAULT, App_ResourceClass(RC_GRAPHIC));
473 // Ensure the found path is absolute.
474 foundPath = App_BasePath() / foundPath;
475
476 return GL_LoadImage(image, foundPath)? External : None;
477 }
478 catch (FS1::NotFoundError const&)
479 {} // Ignore this error.
480
481 // Try again without the suffix?
482 if (!optionalSuffix.empty())
483 {
484 try
485 {
486 String foundPath = App_FileSystem().findPath(de::Uri(encodedSearchPath, RC_GRAPHIC),
487 RLF_DEFAULT, App_ResourceClass(RC_GRAPHIC));
488 // Ensure the found path is absolute.
489 foundPath = App_BasePath() / foundPath;
490
491 return GL_LoadImage(image, foundPath)? External : None;
492 }
493 catch (FS1::NotFoundError const&)
494 {} // Ignore this error.
495 }
496
497 return None;
498 }
499
500 /**
501 * Draw the component image @a src into the composite @a dst.
502 *
503 * @param dst The composite buffer (drawn to).
504 * @param dstDimensions Pixel dimensions of @a dst.
505 * @param src The component image to be composited (read from).
506 * @param srcDimensions Pixel dimensions of @a src.
507 * @param origin Coordinates (topleft) in @a dst to draw @a src.
508 *
509 * @todo Optimize: Should be redesigned to composite whole rows -ds
510 */
compositePaletted(dbyte * dst,Vector2ui const & dstDimensions,IByteArray const & src,Vector2ui const & srcDimensions,Vector2i const & origin)511 static void compositePaletted(dbyte *dst, Vector2ui const &dstDimensions,
512 IByteArray const &src, Vector2ui const &srcDimensions, Vector2i const &origin)
513 {
514 if (dstDimensions == Vector2ui()) return;
515 if (srcDimensions == Vector2ui()) return;
516
517 int const srcW = srcDimensions.x;
518 int const srcH = srcDimensions.y;
519 size_t const srcPels = srcW * srcH;
520
521 int const dstW = dstDimensions.x;
522 int const dstH = dstDimensions.y;
523 size_t const dstPels = dstW * dstH;
524
525 int dstX, dstY;
526
527 for (int srcY = 0; srcY < srcH; ++srcY)
528 for (int srcX = 0; srcX < srcW; ++srcX)
529 {
530 dstX = origin.x + srcX;
531 dstY = origin.y + srcY;
532 if (dstX < 0 || dstX >= dstW) continue;
533 if (dstY < 0 || dstY >= dstH) continue;
534
535 dbyte srcAlpha;
536 src.get(srcY * srcW + srcX + srcPels, &srcAlpha, 1);
537 if (srcAlpha)
538 {
539 src.get(srcY * srcW + srcX, &dst[dstY * dstW + dstX], 1);
540 dst[dstY * dstW + dstX + dstPels] = srcAlpha;
541 }
542 }
543 }
544
545 /// Returns a palette translation id for the given class and map.
546 /// Note that a zero-length id is returned when @a tclass =0 and @a tmap =0
toTranslationId(int tclass,int tmap)547 static String toTranslationId(int tclass, int tmap)
548 {
549 #define NUM_TRANSLATION_CLASSES 3
550 #define NUM_TRANSLATION_MAPS_PER_CLASS 7
551
552 // Is translation unnecessary?
553 if (!tclass && !tmap) return String();
554
555 int trans = de::max(0, NUM_TRANSLATION_MAPS_PER_CLASS * tclass + tmap - 1);
556 LOGDEV_RES_XVERBOSE("tclass=%i tmap=%i => TransPal# %i", tclass << tmap << trans);
557 return String::number(trans);
558
559 #undef NUM_TRANSLATION_MAPS_PER_CLASS
560 #undef NUM_TRANSLATION_CLASSES
561 }
562
loadAndTranslatePatch(IByteArray const & data,colorpaletteid_t palId,int tclass=0,int tmap=0)563 static Block loadAndTranslatePatch(IByteArray const &data, colorpaletteid_t palId,
564 int tclass = 0, int tmap = 0)
565 {
566 res::ColorPalette &palette = App_Resources().colorPalettes().colorPalette(palId);
567 if (res::ColorPaletteTranslation const *xlat = palette.translation(toTranslationId(tclass, tmap)))
568 {
569 return res::Patch::load(data, *xlat, res::Patch::ClipToLogicalDimensions);
570 }
571 else
572 {
573 return res::Patch::load(data, res::Patch::ClipToLogicalDimensions);
574 }
575 }
576
loadPatch(image_t & image,FileHandle & hndl,int tclass=0,int tmap=0,int border=0)577 static Source loadPatch(image_t &image, FileHandle &hndl, int tclass = 0,
578 int tmap = 0, int border = 0)
579 {
580 LOG_AS("image_t::loadPatch");
581
582 if (Image_LoadFromFile(image, hndl))
583 {
584 return External;
585 }
586
587 File1 &file = hndl.file();
588 ByteRefArray fileData = ByteRefArray(file.cache(), file.size());
589
590 // A DOOM patch?
591 if (Patch::recognize(fileData))
592 {
593 try
594 {
595 colorpaletteid_t colorPaletteId = App_Resources().colorPalettes().defaultColorPalette();
596
597 Block patchImg = loadAndTranslatePatch(fileData, colorPaletteId, tclass, tmap);
598 PatchMetadata info = Patch::loadMetadata(fileData);
599
600 Image_Init(image);
601 image.size = Vector2ui(info.logicalDimensions.x + border*2,
602 info.logicalDimensions.y + border*2);
603 image.pixelSize = 1;
604 image.paletteId = colorPaletteId;
605
606 image.pixels = (uint8_t *) M_Calloc(2 * image.size.x * image.size.y);
607
608 compositePaletted(image.pixels, image.size,
609 patchImg, info.logicalDimensions, Vector2i(border, border));
610
611 if (palettedIsMasked(image.pixels, image.size.x, image.size.y))
612 {
613 image.flags |= IMGF_IS_MASKED;
614 }
615
616 return Original;
617 }
618 catch (IByteArray::OffsetError const &)
619 {
620 LOG_RES_WARNING("File \"%s:%s\" does not appear to be a valid Patch")
621 << NativePath(file.container().composePath()).pretty()
622 << NativePath(file.composePath()).pretty();
623 }
624 }
625
626 file.unlock();
627 return None;
628 }
629
loadPatchComposite(image_t & image,Texture const & tex,bool maskZero=false,bool useZeroOriginIfOneComponent=false)630 static Source loadPatchComposite(image_t &image, Texture const &tex,
631 bool maskZero = false, bool useZeroOriginIfOneComponent = false)
632 {
633 LOG_AS("image_t::loadPatchComposite");
634
635 Image_Init(image);
636 image.pixelSize = 1;
637 image.size = Vector2ui(tex.width(), tex.height());
638 image.paletteId = App_Resources().colorPalettes().defaultColorPalette();
639
640 image.pixels = (uint8_t *) M_Calloc(2 * image.size.x * image.size.y);
641
642 res::Composite const &texDef = *reinterpret_cast<res::Composite *>(tex.userDataPointer());
643 DENG2_FOR_EACH_CONST(res::Composite::Components, i, texDef.components())
644 {
645 File1 &file = App_FileSystem().lump(i->lumpNum());
646 ByteRefArray fileData = ByteRefArray(file.cache(), file.size());
647
648 // A DOOM patch?
649 if (Patch::recognize(fileData))
650 {
651 try
652 {
653 Patch::Flags loadFlags;
654 if (maskZero) loadFlags |= Patch::MaskZero;
655
656 Block patchImg = Patch::load(fileData, loadFlags);
657 PatchMetadata info = Patch::loadMetadata(fileData);
658
659 Vector2i origin = i->origin();
660 if (useZeroOriginIfOneComponent && texDef.componentCount() == 1)
661 {
662 origin = Vector2i(0, 0);
663 }
664
665 // Draw the patch in the buffer.
666 compositePaletted(image.pixels, image.size,
667 patchImg, info.dimensions, origin);
668 }
669 catch (IByteArray::OffsetError const &)
670 {} // Ignore this error.
671 }
672
673 file.unlock();
674 }
675
676 if (maskZero || palettedIsMasked(image.pixels, image.size.x, image.size.y))
677 {
678 image.flags |= IMGF_IS_MASKED;
679 }
680
681 // For debug:
682 // GL_DumpImage(&image, Str_Text(GL_ComposeCacheNameForTexture(tex)));
683
684 return Original;
685 }
686
loadFlat(image_t & image,FileHandle & hndl)687 static Source loadFlat(image_t &image, FileHandle &hndl)
688 {
689 if (Image_LoadFromFile(image, hndl))
690 {
691 return External;
692 }
693
694 // A DOOM flat.
695 Image_Init(image);
696
697 /// @todo not all flats are 64x64!
698 image.size = Vector2ui(64, 64);
699 image.pixelSize = 1;
700 image.paletteId = App_Resources().colorPalettes().defaultColorPalette();
701
702 File1 &file = hndl.file();
703 size_t fileLength = hndl.length();
704
705 size_t bufSize = de::max(fileLength, (size_t) image.size.x * image.size.y);
706 image.pixels = (uint8_t *) M_Malloc(bufSize);
707
708 if (fileLength < bufSize)
709 {
710 std::memset(image.pixels, 0, bufSize);
711 }
712
713 // Load the raw image data.
714 file.read(image.pixels, 0, fileLength);
715 return Original;
716 }
717
loadDetail(image_t & image,FileHandle & hndl)718 static Source loadDetail(image_t &image, FileHandle &hndl)
719 {
720 if (Image_LoadFromFile(image, hndl))
721 {
722 return Original;
723 }
724
725 // It must be an old-fashioned "raw" image.
726 Image_Init(image);
727
728 // How big is it?
729 File1 &file = hndl.file();
730 size_t fileLength = hndl.length();
731 switch (fileLength)
732 {
733 case 256 * 256: image.size.x = image.size.y = 256; break;
734 case 128 * 128: image.size.x = image.size.y = 128; break;
735 case 64 * 64: image.size.x = image.size.y = 64; break;
736 default:
737 throw Error("image_t::loadDetail", "Must be 256x256, 128x128 or 64x64.");
738 }
739
740 image.pixelSize = 1;
741 size_t bufSize = (size_t) image.size.x * image.size.y;
742 image.pixels = (uint8_t *) M_Malloc(bufSize);
743
744 if (fileLength < bufSize)
745 {
746 std::memset(image.pixels, 0, bufSize);
747 }
748
749 // Load the raw image data.
750 file.read(image.pixels, fileLength);
751 return Original;
752 }
753
GL_LoadSourceImage(image_t & image,ClientTexture const & tex,TextureVariantSpec const & spec)754 Source GL_LoadSourceImage(image_t &image, ClientTexture const &tex,
755 TextureVariantSpec const &spec)
756 {
757 de::FS1 &fileSys = App_FileSystem();
758 auto &cfg = R_Config();
759
760 Source source = None;
761 variantspecification_t const &vspec = spec.variant;
762 if (!tex.manifest().schemeName().compareWithoutCase("Textures"))
763 {
764 // Attempt to load an external replacement for this composite texture?
765 if (cfg.noHighResTex->value().isFalse() &&
766 (loadExtAlways || cfg.highResWithPWAD->value().isTrue() || !tex.isFlagged(Texture::Custom)))
767 {
768 // First try the textures scheme.
769 de::Uri uri = tex.manifest().composeUri();
770 source = loadExternalTexture(image, uri.compose(), "-ck");
771 }
772
773 if (source == None)
774 {
775 if (TC_SKYSPHERE_DIFFUSE != vspec.context)
776 {
777 source = loadPatchComposite(image, tex);
778 }
779 else
780 {
781 bool const zeroMask = (vspec.flags & TSF_ZEROMASK) != 0;
782 bool const useZeroOriginIfOneComponent = true;
783 source = loadPatchComposite(image, tex, zeroMask, useZeroOriginIfOneComponent);
784 }
785 }
786 }
787 else if (!tex.manifest().schemeName().compareWithoutCase("Flats"))
788 {
789 // Attempt to load an external replacement for this flat?
790 if (cfg.noHighResTex->value().isFalse() &&
791 (loadExtAlways || cfg.highResWithPWAD->value().isTrue() || !tex.isFlagged(Texture::Custom)))
792 {
793 // First try the flats scheme.
794 de::Uri uri = tex.manifest().composeUri();
795 source = loadExternalTexture(image, uri.compose(), "-ck");
796
797 if (source == None)
798 {
799 // How about the old-fashioned "flat-name" in the textures scheme?
800 source = loadExternalTexture(image, "Textures:flat-" + uri.path().toStringRef(), "-ck");
801 }
802 }
803
804 if (source == None)
805 {
806 if (tex.manifest().hasResourceUri())
807 {
808 de::Uri resourceUri = tex.manifest().resourceUri();
809 if (!resourceUri.scheme().compareWithoutCase("LumpIndex"))
810 {
811 try
812 {
813 lumpnum_t const lumpNum = resourceUri.path().toString().toInt();
814 FileHandle &hndl = fileSys.openLump(fileSys.lump(lumpNum));
815
816 source = loadFlat(image, hndl);
817
818 fileSys.releaseFile(hndl.file());
819 delete &hndl;
820 }
821 catch (LumpIndex::NotFoundError const&)
822 {} // Ignore this error.
823 }
824 }
825 }
826 }
827 else if (!tex.manifest().schemeName().compareWithoutCase("Patches"))
828 {
829 int tclass = 0, tmap = 0;
830 if (vspec.flags & TSF_HAS_COLORPALETTE_XLAT)
831 {
832 tclass = vspec.tClass;
833 tmap = vspec.tMap;
834 }
835
836 // Attempt to load an external replacement for this patch?
837 if (cfg.noHighResTex->value().isFalse() &&
838 (loadExtAlways || cfg.highResWithPWAD->value().isTrue() || !tex.isFlagged(Texture::Custom)))
839 {
840 de::Uri uri = tex.manifest().composeUri();
841 source = loadExternalTexture(image, uri.compose(), "-ck");
842 }
843
844 if (source == None)
845 {
846 if (tex.manifest().hasResourceUri())
847 {
848 de::Uri resourceUri = tex.manifest().resourceUri();
849 if (!resourceUri.scheme().compareWithoutCase("LumpIndex"))
850 {
851 try
852 {
853 lumpnum_t const lumpNum = resourceUri.path().toString().toInt();
854 FileHandle &hndl = fileSys.openLump(fileSys.lump(lumpNum));
855
856 source = loadPatch(image, hndl, tclass, tmap, vspec.border);
857
858 fileSys.releaseFile(hndl.file());
859 delete &hndl;
860 }
861 catch (LumpIndex::NotFoundError const&)
862 {} // Ignore this error.
863 }
864 }
865 }
866 }
867 else if (!tex.manifest().schemeName().compareWithoutCase("Sprites"))
868 {
869 int tclass = 0, tmap = 0;
870 if (vspec.flags & TSF_HAS_COLORPALETTE_XLAT)
871 {
872 tclass = vspec.tClass;
873 tmap = vspec.tMap;
874 }
875
876 // Attempt to load an external replacement for this sprite?
877 if (cfg.noHighResPatches->value().isFalse())
878 {
879 de::Uri uri = tex.manifest().composeUri();
880
881 // Prefer psprite or translated versions if available.
882 if (TC_PSPRITE_DIFFUSE == vspec.context)
883 {
884 source = loadExternalTexture(image, "Patches:" + uri.path() + "-hud", "-ck");
885 }
886 else if (tclass || tmap)
887 {
888 source = loadExternalTexture(image, "Patches:" + uri.path() + String("-table%1%2").arg(tclass).arg(tmap), "-ck");
889 }
890
891 if (!source)
892 {
893 source = loadExternalTexture(image, "Patches:" + uri.path(), "-ck");
894 }
895 }
896
897 if (source == None)
898 {
899 if (tex.manifest().hasResourceUri())
900 {
901 de::Uri resourceUri = tex.manifest().resourceUri();
902 if (!resourceUri.scheme().compareWithoutCase("LumpIndex"))
903 {
904 try
905 {
906 lumpnum_t const lumpNum = resourceUri.path().toString().toInt();
907 FileHandle &hndl = fileSys.openLump(fileSys.lump(lumpNum));
908
909 source = loadPatch(image, hndl, tclass, tmap, vspec.border);
910
911 fileSys.releaseFile(hndl.file());
912 delete &hndl;
913 }
914 catch (LumpIndex::NotFoundError const&)
915 {} // Ignore this error.
916 }
917 }
918 }
919 }
920 else if (!tex.manifest().schemeName().compareWithoutCase("Details"))
921 {
922 if (tex.manifest().hasResourceUri())
923 {
924 de::Uri resourceUri = tex.manifest().resourceUri();
925 if (resourceUri.scheme().compareWithoutCase("Lumps"))
926 {
927 source = loadExternalTexture(image, resourceUri.compose());
928 }
929 else
930 {
931 lumpnum_t const lumpNum = fileSys.lumpNumForName(resourceUri.path());
932 try
933 {
934 File1 &lump = fileSys.lump(lumpNum);
935 FileHandle &hndl = fileSys.openLump(lump);
936
937 source = loadDetail(image, hndl);
938
939 fileSys.releaseFile(hndl.file());
940 delete &hndl;
941 }
942 catch (LumpIndex::NotFoundError const&)
943 {} // Ignore this error.
944 }
945 }
946 }
947 else
948 {
949 if (tex.manifest().hasResourceUri())
950 {
951 de::Uri resourceUri = tex.manifest().resourceUri();
952 source = loadExternalTexture(image, resourceUri.compose());
953 }
954 }
955 return source;
956 }
957