GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/image/image.cpp Lines: 52 156 33.3 %
Date: 2021-03-17 Branches: 13 96 13.5 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *  Copyright (C) 2019-2021  Andrei Karas
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
#include "resources/image/image.h"
25
26
#include "logger.h"
27
28
#ifdef USE_OPENGL
29
#include "resources/openglimagehelper.h"
30
#endif  // USE_OPENGL
31
32
#include "resources/memorymanager.h"
33
#include "resources/sdlimagehelper.h"
34
35
#include "resources/image/subimage.h"
36
37
#include "resources/resourcemanager/resourcemanager.h"
38
39
#include "utils/cast.h"
40
#include "utils/sdlcheckutils.h"
41
42
PRAGMA48(GCC diagnostic push)
43
PRAGMA48(GCC diagnostic ignored "-Wshadow")
44
#ifdef USE_SDL2
45
#include <SDL2_rotozoom.h>
46
#else  // USE_SDL2
47
#include <SDL_rotozoom.h>
48
#endif  // USE_SDL2
49
PRAGMA48(GCC diagnostic pop)
50
51
#include "debug.h"
52
53
#ifdef UNITTESTS
54
143
Image::Image(const int width,
55
143
             const int height) :
56
    Resource(),
57
#ifdef USE_OPENGL
58
    mGLImage(0),
59
    mTexWidth(0),
60
    mTexHeight(0),
61
#endif  // USE_OPENGL
62
    mBounds(),
63
    mAlpha(1.0F),
64
    mSDLSurface(nullptr),
65
#ifdef USE_SDL2
66
    mTexture(nullptr),
67
#endif  // USE_SDL2
68
    mAlphaChannel(nullptr),
69
    mAlphaCache(),
70
    mLoaded(false),
71
    mHasAlphaChannel(false),
72
    mUseAlphaCache(false),
73
    mIsAlphaVisible(true),
74
429
    mIsAlphaCalculated(false)
75
{
76
    mBounds.x = 0;
77
    mBounds.y = 0;
78
143
    mBounds.w = width;
79
143
    mBounds.h = height;
80
143
}
81
#endif  // UNITTESTS
82
83
#ifdef USE_SDL2
84
Image::Image(SDL_Texture *restrict const image,
85
             const int width, const int height) :
86
    Resource(),
87
#ifdef USE_OPENGL
88
    mGLImage(0),
89
    mTexWidth(0),
90
    mTexHeight(0),
91
#endif  // USE_OPENGL
92
    mBounds(),
93
    mAlpha(1.0F),
94
    mSDLSurface(nullptr),
95
    mTexture(image),
96
    mAlphaChannel(nullptr),
97
    mAlphaCache(),
98
    mLoaded(false),
99
    mHasAlphaChannel(false),
100
    mUseAlphaCache(false),
101
    mIsAlphaVisible(true),
102
    mIsAlphaCalculated(false)
103
{
104
#ifdef DEBUG_IMAGES
105
    logger->log("created image: %p",
106
        static_cast<void*>(this));
107
#endif  // DEBUG_IMAGES
108
109
    mBounds.x = 0;
110
    mBounds.y = 0;
111
112
    if (mTexture)
113
    {
114
        mBounds.w = CAST_U16(width);
115
        mBounds.h = CAST_U16(height);
116
117
        mLoaded = true;
118
    }
119
    else
120
    {
121
        mBounds.w = 0;
122
        mBounds.h = 0;
123
    }
124
}
125
#endif  // USE_SDL2
126
127
30866
Image::Image(SDL_Surface *restrict const image, const bool hasAlphaChannel0,
128
30866
             uint8_t *restrict const alphaChannel) :
129
    Resource(),
130
#ifdef USE_OPENGL
131
    mGLImage(0),
132
    mTexWidth(0),
133
    mTexHeight(0),
134
#endif  // USE_OPENGL
135
    mBounds(),
136
    mAlpha(1.0F),
137
    mSDLSurface(image),
138
#ifdef USE_SDL2
139
    mTexture(nullptr),
140
#endif  // USE_SDL2
141
    mAlphaChannel(alphaChannel),
142
    mAlphaCache(),
143
    mLoaded(false),
144
    mHasAlphaChannel(hasAlphaChannel0),
145
    mUseAlphaCache(SDLImageHelper::mEnableAlphaCache),
146
    mIsAlphaVisible(hasAlphaChannel0),
147
92598
    mIsAlphaCalculated(false)
148
{
149
#ifdef DEBUG_IMAGES
150
    logger->log("created image: %p", static_cast<void*>(this));
151
#endif  // DEBUG_IMAGES
152
153
    mBounds.x = 0;
154
    mBounds.y = 0;
155
156
30866
    if (mSDLSurface != nullptr)
157
    {
158
30866
        mBounds.w = CAST_U16(mSDLSurface->w);
159
30866
        mBounds.h = CAST_U16(mSDLSurface->h);
160
161
30866
        mLoaded = true;
162
    }
163
    else
164
    {
165
        mBounds.w = 0;
166
        mBounds.h = 0;
167
    }
168
30866
}
169
170
#ifdef USE_OPENGL
171
Image::Image(const GLuint glimage, const int width, const int height,
172
             const int texWidth, const int texHeight) :
173
    Resource(),
174
    mGLImage(glimage),
175
    mTexWidth(texWidth),
176
    mTexHeight(texHeight),
177
    mBounds(),
178
    mAlpha(1.0F),
179
    mSDLSurface(nullptr),
180
#ifdef USE_SDL2
181
    mTexture(nullptr),
182
#endif  // USE_SDL2
183
    mAlphaChannel(nullptr),
184
    mAlphaCache(),
185
    mLoaded(false),
186
    mHasAlphaChannel(true),
187
    mUseAlphaCache(false),
188
    mIsAlphaVisible(true),
189
    mIsAlphaCalculated(false)
190
{
191
#ifdef DEBUG_IMAGES
192
    logger->log("created image: %p", static_cast<void*>(this));
193
#endif  // DEBUG_IMAGES
194
195
    mBounds.x = 0;
196
    mBounds.y = 0;
197
    mBounds.w = CAST_U16(width);
198
    mBounds.h = CAST_U16(height);
199
200
    if (mGLImage != 0U)
201
    {
202
        mLoaded = true;
203
    }
204
}
205
#endif  // USE_OPENGL
206
207
95030
Image::~Image()
208
{
209
#ifdef DEBUG_IMAGES
210
    logger->log("delete image: %p", static_cast<void*>(this));
211
    logger->log("  %s, %s", mIdPath.c_str(), mSource.c_str());
212
#endif  // DEBUG_IMAGES
213
214
31009
    unload();
215
33012
}
216
217
30866
void Image::SDLCleanCache()
218
{
219
30866
    for (std::map<float, SDL_Surface*>::iterator
220
92598
         i = mAlphaCache.begin(), i_end = mAlphaCache.end();
221
         i != i_end; ++i)
222
    {
223
        if (mSDLSurface != i->second)
224
            ResourceManager::scheduleDelete(i->second);
225
        i->second = nullptr;
226
    }
227
61732
    mAlphaCache.clear();
228
30866
}
229
230
31009
void Image::unload()
231
{
232
31009
    mLoaded = false;
233
234
31009
    if (mSDLSurface != nullptr)
235
    {
236
1860
        SDLCleanCache();
237
        // Free the image surface.
238
1860
        MSDL_FreeSurface(mSDLSurface);
239
1860
        mSDLSurface = nullptr;
240
241
1860
        delete [] mAlphaChannel;
242
1860
        mAlphaChannel = nullptr;
243
    }
244
#ifdef USE_SDL2
245
    if (mTexture)
246
    {
247
        SDL_DestroyTexture(mTexture);
248
        mTexture = nullptr;
249
    }
250
#endif  // USE_SDL2
251
252
#ifdef USE_OPENGL
253
31009
    if (mGLImage != 0U)
254
    {
255
        glDeleteTextures(1, &mGLImage);
256
        mGLImage = 0;
257
#ifdef DEBUG_OPENGL_LEAKS
258
        if (textures_count > 0)
259
            textures_count --;
260
#endif  // DEBUG_OPENGL_LEAKS
261
    }
262
#endif  // USE_OPENGL
263
31009
}
264
265
29006
bool Image::hasAlphaChannel() const
266
{
267
29006
    if (mLoaded)
268
29006
        return mHasAlphaChannel;
269
270
#ifdef USE_OPENGL
271
    if (OpenGLImageHelper::mUseOpenGL != RENDER_SOFTWARE)
272
        return true;
273
#endif  // USE_OPENGL
274
275
    return false;
276
}
277
278
SDL_Surface *Image::getByAlpha(const float alpha)
279
{
280
    const std::map<float, SDL_Surface*>::const_iterator
281
        it = mAlphaCache.find(alpha);
282
    if (it != mAlphaCache.end())
283
        return (*it).second;
284
    return nullptr;
285
}
286
287
5084
void Image::setAlpha(const float alpha)
288
{
289

5084
    if (mAlpha == alpha || !ImageHelper::mEnableAlpha)
290
        return;
291
292
    if (alpha < 0.0F || alpha > 1.0F)
293
        return;
294
295
    if (mSDLSurface != nullptr)
296
    {
297
        if (mUseAlphaCache)
298
        {
299
            SDL_Surface *surface = getByAlpha(mAlpha);
300
            if (surface == nullptr)
301
            {
302
                if (mAlphaCache.size() > 100)
303
                {
304
#ifdef DEBUG_ALPHA_CACHE
305
                    logger->log("cleanCache");
306
                    for (std::map<float, SDL_Surface*>::const_iterator
307
                         i = mAlphaCache.begin(), i_end = mAlphaCache.end();
308
                         i != i_end; ++i)
309
                    {
310
                        logger->log("alpha: " + toString(i->first));
311
                    }
312
#endif  // DEBUG_ALPHA_CACHE
313
314
                    SDLCleanCache();
315
                }
316
                surface = mSDLSurface;
317
                if (surface != nullptr)
318
                    mAlphaCache[mAlpha] = surface;
319
            }
320
            else
321
            {
322
                logger->log("cache bug");
323
            }
324
325
            surface = getByAlpha(alpha);
326
            if (surface != nullptr)
327
            {
328
                if (mSDLSurface == surface)
329
                    logger->log("bug");
330
                mAlphaCache.erase(alpha);
331
                mSDLSurface = surface;
332
                mAlpha = alpha;
333
                return;
334
            }
335
            mSDLSurface = SDLImageHelper::SDLDuplicateSurface(mSDLSurface);
336
            if (mSDLSurface == nullptr)
337
                return;
338
        }
339
340
        mAlpha = alpha;
341
342
        if (!mHasAlphaChannel)
343
        {
344
#ifdef USE_SDL2
345
            SDL_SetSurfaceAlphaMod(mSDLSurface,
346
                CAST_U8(255 * mAlpha));
347
#else  // USE_SDL2
348
349
            // Set the alpha value this image is drawn at
350
            SDL_SetAlpha(mSDLSurface, SDL_SRCALPHA,
351
                CAST_U8(255 * mAlpha));
352
#endif  // USE_SDL2
353
        }
354
        else
355
        {
356
            if (SDL_MUSTLOCK(mSDLSurface))
357
                SDL_LockSurface(mSDLSurface);
358
359
            const int bx = mBounds.x;
360
            const int by = mBounds.y;
361
            const int bw = mBounds.w;
362
            const int bh = mBounds.h;
363
            const int bxw = bx + bw;
364
            const int sw = mSDLSurface->w;
365
            const int maxHeight = std::min(by + bh, mSDLSurface->h);
366
            const int maxWidth = std::min(bxw, sw);
367
            const int i1 = by * sw + bx;
368
            const SDL_PixelFormat * const fmt = mSDLSurface->format;
369
            const uint32_t amask = fmt->Amask;
370
            const uint32_t invMask = ~fmt->Amask;
371
            const uint8_t aloss = fmt->Aloss;
372
            const uint8_t ashift = fmt->Ashift;
373
374
            if ((bx == 0) && bxw == sw)
375
            {
376
                const int i2 = (maxHeight - 1) * sw + maxWidth - 1;
377
                for (int i = i1; i <= i2; i++)
378
                {
379
                    const uint8_t sourceAlpha = mAlphaChannel[i];
380
                    if (sourceAlpha > 0)
381
                    {
382
                        const uint8_t a = CAST_U8(
383
                            static_cast<float>(sourceAlpha) * mAlpha);
384
385
                        uint32_t c = (static_cast<uint32_t*>(
386
                            mSDLSurface->pixels))[i];
387
                        c &= invMask;
388
                        c |= ((a >> aloss) << ashift & amask);
389
                        (static_cast<uint32_t*>(mSDLSurface->pixels))[i] = c;
390
                    }
391
                }
392
            }
393
            else
394
            {
395
                for (int y = by; y < maxHeight; y ++)
396
                {
397
                    const int idx = y * sw;
398
                    const int x1 = idx + bx;
399
                    const int x2 = idx + maxWidth;
400
                    for (int i = x1; i < x2; i ++)
401
                    {
402
                        const uint8_t sourceAlpha = mAlphaChannel[i];
403
                        if (sourceAlpha > 0)
404
                        {
405
                            const uint8_t a = CAST_U8(
406
                                static_cast<float>(sourceAlpha) * mAlpha);
407
408
                            uint32_t c = (static_cast<uint32_t*>(
409
                                mSDLSurface->pixels))[i];
410
                            c &= invMask;
411
                            c |= ((a >> aloss) << ashift & amask);
412
                            (static_cast<uint32_t*>(
413
                                mSDLSurface->pixels))[i] = c;
414
                        }
415
                    }
416
                }
417
            }
418
419
            if (SDL_MUSTLOCK(mSDLSurface))
420
                SDL_UnlockSurface(mSDLSurface);
421
        }
422
    }
423
#ifdef USE_SDL2
424
    else if (mTexture)
425
    {
426
        mAlpha = alpha;
427
        SDL_SetTextureAlphaMod(mTexture,
428
            CAST_U8(255 * mAlpha));
429
    }
430
#endif  // USE_SDL2
431
    else
432
    {
433
        mAlpha = alpha;
434
    }
435
}
436
437
Image* Image::SDLgetScaledImage(const int width, const int height) const
438
{
439
    // No scaling on incorrect new values.
440
    if (width == 0 || height == 0)
441
        return nullptr;
442
443
    // No scaling when there is ... no different given size ...
444
    if (width == mBounds.w && height == mBounds.h)
445
        return nullptr;
446
447
    Image* scaledImage = nullptr;
448
449
    if (mSDLSurface != nullptr)
450
    {
451
        SDL_Surface *const scaledSurface = zoomSurface(mSDLSurface,
452
                    static_cast<double>(width) / mBounds.w,
453
                    static_cast<double>(height) / mBounds.h,
454
                    1);
455
456
        // The load function takes care of the SDL<->OpenGL implementation
457
        // and about freeing the given SDL_surface*.
458
        if (scaledSurface != nullptr)
459
        {
460
            scaledImage = imageHelper->loadSurface(scaledSurface);
461
            MSDL_FreeSurface(scaledSurface);
462
        }
463
    }
464
    return scaledImage;
465
}
466
467
29006
Image *Image::getSubImage(const int x, const int y,
468
                          const int width, const int height)
469
{
470
    // Create a new clipped sub-image
471
#ifdef USE_OPENGL
472
29006
    const RenderType mode = OpenGLImageHelper::mUseOpenGL;
473
29006
    if (mode == RENDER_NORMAL_OPENGL ||
474
29006
        mode == RENDER_SAFE_OPENGL ||
475
29006
        mode == RENDER_GLES_OPENGL ||
476
58012
        mode == RENDER_GLES2_OPENGL ||
477
        mode == RENDER_MODERN_OPENGL)
478
    {
479
        return new SubImage(this,
480
            mGLImage,
481
            x, y,
482
            width, height,
483
            mTexWidth, mTexHeight);
484
    }
485
#endif  // USE_OPENGL
486
487
#ifdef USE_SDL2
488
#ifndef USE_OPENGL
489
    const RenderType mode = ImageHelper::mUseOpenGL;
490
#endif  // USE_OPENGL
491
492
    if (mode == RENDER_SOFTWARE)
493
        return new SubImage(this, mSDLSurface, x, y, width, height);
494
    else
495
        return new SubImage(this, mTexture, x, y, width, height);
496
#else  // USE_SDL2
497
498
29006
    return new SubImage(this, mSDLSurface, x, y, width, height);
499
#endif  // USE_SDL2
500
}
501
502
29006
void Image::SDLTerminateAlphaCache()
503
{
504
29006
    SDLCleanCache();
505
29006
    mUseAlphaCache = false;
506
29006
}
507
508
int Image::calcMemoryLocal() const
509
{
510
    // +++ this calculation can be wrong for SDL2
511
    int sz = static_cast<int>(sizeof(Image) +
512
        sizeof(std::map<float, SDL_Surface*>)) +
513
        Resource::calcMemoryLocal();
514
    if (mSDLSurface != nullptr)
515
    {
516
        sz += CAST_S32(mAlphaCache.size()) *
517
            MemoryManager::getSurfaceSize(mSDLSurface);
518
    }
519
    return sz;
520
}
521
522
#ifdef USE_OPENGL
523
41409
void Image::decRef()
524
{
525

41409
    if ((mGLImage != 0U) && mRefCount <= 1)
526
        OpenGLImageHelper::invalidate(mGLImage);
527
41409
    Resource::decRef();
528
41409
}
529
#endif  // USE_OPENGL