1 /*
2    Copyright (C) 2003 - 2018 the Battle for Wesnoth Project https://www.wesnoth.org/
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY.
10 
11    See the COPYING file for more details.
12 */
13 
14 #include "sdl/surface.hpp"
15 
16 #include "sdl/rect.hpp"
17 #include "video.hpp"
18 
19 #include <iostream>
20 
__anonee5f169e0102() 21 const SDL_PixelFormat surface::neutral_pixel_format = []() {
22 #if SDL_VERSION_ATLEAST(2, 0, 6)
23 	return *SDL_CreateRGBSurfaceWithFormat(0, 1, 1, 32, SDL_PIXELFORMAT_ARGB8888)->format;
24 #else
25 	return *SDL_CreateRGBSurface(0, 1, 1, 32, SDL_RED_MASK, SDL_GREEN_MASK, SDL_BLUE_MASK, SDL_ALPHA_MASK)->format;
26 #endif
27 }();
28 
surface(SDL_Surface * surf)29 surface::surface(SDL_Surface* surf)
30 	: surface_(surf)
31 {
32 	make_neutral(); // EXTREMELY IMPORTANT!
33 }
34 
surface(int w,int h)35 surface::surface(int w, int h)
36 	: surface_(nullptr)
37 {
38 	if (w < 0 || h < 0) {
39 		throw std::invalid_argument("Creating surface with negative dimensions");
40 	}
41 
42 #if SDL_VERSION_ATLEAST(2, 0, 6)
43 	surface_ = SDL_CreateRGBSurfaceWithFormat(0, w, h, neutral_pixel_format.BitsPerPixel, neutral_pixel_format.format);
44 #else
45 	surface_ = SDL_CreateRGBSurface(0, w, h,
46 		neutral_pixel_format.BitsPerPixel,
47 		neutral_pixel_format.Rmask,
48 		neutral_pixel_format.Gmask,
49 		neutral_pixel_format.Bmask,
50 		neutral_pixel_format.Amask);
51 #endif
52 	if(!surface_) {
53 		//TODO: maybe use one of our our custom logstreams instead, not sure which one would fit.
54 		std::cerr << "Failed to create a surface of size " << w << "x" << h << " Reason: " << SDL_GetError() << "\n";
55 	}
56 }
57 
is_neutral() const58 bool surface::is_neutral() const
59 {
60 	return surface_
61 		&& SDL_ISPIXELFORMAT_INDEXED(surface_->format->format) == SDL_FALSE
62 		&&  surface_->format->BytesPerPixel == 4
63 		&&  surface_->format->Rmask == SDL_RED_MASK
64 		&& (surface_->format->Amask | SDL_ALPHA_MASK) == SDL_ALPHA_MASK;
65 }
66 
make_neutral()67 surface& surface::make_neutral()
68 {
69 	if(surface_ && !is_neutral()) {
70 		SDL_Surface* res = SDL_ConvertSurface(surface_, &neutral_pixel_format, 0);
71 
72 		// Ensure we don't leak memory with the old surface.
73 		free_surface();
74 
75 		surface_ = res;
76 	}
77 
78 	return *this;
79 }
80 
clone() const81 surface surface::clone() const
82 {
83 	// Use SDL_ConvertSurface to make a copy
84 	return surface(SDL_ConvertSurface(surface_, &neutral_pixel_format, 0));
85 }
86 
assign_surface_internal(SDL_Surface * surf)87 void surface::assign_surface_internal(SDL_Surface* surf)
88 {
89 	add_surface_ref(surf); // Needs to be done before assignment to avoid corruption on "a = a;"
90 	free_surface();
91 	surface_ = surf;
92 	make_neutral(); // EXTREMELY IMPORTANT!
93 }
94 
free_surface()95 void surface::free_surface()
96 {
97 	if(surface_) {
98 		/* Workaround for an SDL bug.
99 		* SDL 2.0.6 frees the blit map unconditionally in SDL_FreeSurface() without checking
100 		* if the reference count has fallen to zero. However, many SDL functions such as
101 		* SDL_ConvertSurface() assume that the blit map is present.
102 		* Thus, we only call SDL_FreeSurface() if this is the last reference to the surface.
103 		* Otherwise we just decrement the reference count ourselves.
104 		*
105 		* - Jyrki, 2017-09-23
106 		*/
107 		if(surface_->refcount > 1 && sdl_get_version() == version_info(2, 0, 6)) {
108 			--surface_->refcount;
109 		} else {
110 			SDL_FreeSurface(surface_);
111 		}
112 	}
113 }
114 
surface_restorer()115 surface_restorer::surface_restorer()
116 	: target_(nullptr)
117 	, rect_(sdl::empty_rect)
118 	, surface_(nullptr)
119 {
120 }
121 
surface_restorer(CVideo * target,const SDL_Rect & rect)122 surface_restorer::surface_restorer(CVideo* target, const SDL_Rect& rect)
123 	: target_(target)
124 	, rect_(rect)
125 	, surface_(nullptr)
126 {
127 	update();
128 }
129 
~surface_restorer()130 surface_restorer::~surface_restorer()
131 {
132 	restore();
133 }
134 
restore(const SDL_Rect & dst) const135 void surface_restorer::restore(const SDL_Rect& dst) const
136 {
137 	if(!surface_) {
138 		return;
139 	}
140 
141 	SDL_Rect dst2 = sdl::intersect_rects(dst, rect_);
142 	if(dst2.w == 0 || dst2.h == 0) {
143 		return;
144 	}
145 
146 	SDL_Rect src = dst2;
147 	src.x -= rect_.x;
148 	src.y -= rect_.y;
149 	sdl_blit(surface_, &src, target_->getSurface(), &dst2);
150 }
151 
restore() const152 void surface_restorer::restore() const
153 {
154 	if(!surface_) {
155 		return;
156 	}
157 
158 	SDL_Rect dst = rect_;
159 	sdl_blit(surface_, nullptr, target_->getSurface(), &dst);
160 }
161 
update()162 void surface_restorer::update()
163 {
164 	if(rect_.w <= 0 || rect_.h <= 0) {
165 		surface_ = nullptr;
166 	} else {
167 		surface_ = ::get_surface_portion(target_->getSurface(),rect_);
168 	}
169 }
170 
cancel()171 void surface_restorer::cancel()
172 {
173 	surface_ = nullptr;
174 }
175 
operator <(const surface & a,const surface & b)176 bool operator<(const surface& a, const surface& b)
177 {
178 	return a.get() < b.get();
179 }
180