mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Moved image scaling & optimization logic to separate classes
This commit is contained in:
		| @@ -104,6 +104,7 @@ set(vcmiclientcommon_SRCS | ||||
| 	renderSDL/RenderHandler.cpp | ||||
| 	renderSDL/SDLImage.cpp | ||||
| 	renderSDL/SDLImageLoader.cpp | ||||
| 	renderSDL/SDLImageScaler.cpp | ||||
| 	renderSDL/SDLRWwrapper.cpp | ||||
| 	renderSDL/ScreenHandler.cpp | ||||
| 	renderSDL/SDL_Extensions.cpp | ||||
| @@ -313,6 +314,7 @@ set(vcmiclientcommon_HEADERS | ||||
| 	renderSDL/RenderHandler.h | ||||
| 	renderSDL/SDLImage.h | ||||
| 	renderSDL/SDLImageLoader.h | ||||
| 	renderSDL/SDLImageScaler.h | ||||
| 	renderSDL/SDLRWwrapper.h | ||||
| 	renderSDL/ScreenHandler.h | ||||
| 	renderSDL/SDL_Extensions.h | ||||
|   | ||||
| @@ -13,6 +13,7 @@ | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
| #include "../renderSDL/SDL_Extensions.h" | ||||
| #include "../renderSDL/SDLImageScaler.h" | ||||
|  | ||||
| #include <SDL_image.h> | ||||
| #include <SDL_surface.h> | ||||
| @@ -35,9 +36,10 @@ void CanvasImage::scaleTo(const Point & size, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	Point scaledSize = size * GH.screenHandler().getScalingFactor(); | ||||
|  | ||||
| 	auto newSurface = CSDL_Ext::scaleSurface(surface, scaledSize.x, scaledSize.y, algorithm); | ||||
| 	SDLImageScaler scaler(surface); | ||||
| 	scaler.scaleSurface(scaledSize, algorithm); | ||||
| 	SDL_FreeSurface(surface); | ||||
| 	surface = newSurface; | ||||
| 	surface = scaler.acquireResultSurface(); | ||||
| } | ||||
|  | ||||
| void CanvasImage::exportBitmap(const boost::filesystem::path & path) const | ||||
|   | ||||
| @@ -11,6 +11,8 @@ | ||||
| #include "CBitmapFont.h" | ||||
|  | ||||
| #include "SDL_Extensions.h" | ||||
| #include "SDLImageScaler.h" | ||||
|  | ||||
| #include "../CGameInfo.h" | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/Colors.h" | ||||
| @@ -207,9 +209,10 @@ CBitmapFont::CBitmapFont(const std::string & filename): | ||||
|  | ||||
| 		auto filterName = settings["video"]["fontUpscalingFilter"].String(); | ||||
| 		EScalingAlgorithm algorithm = filterNameToEnum.at(filterName); | ||||
| 		auto scaledSurface = CSDL_Ext::scaleSurfaceIntegerFactor(atlasImage, GH.screenHandler().getScalingFactor(), algorithm); | ||||
| 		SDLImageScaler scaler(atlasImage); | ||||
| 		scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), algorithm); | ||||
| 		SDL_FreeSurface(atlasImage); | ||||
| 		atlasImage = scaledSurface; | ||||
| 		atlasImage = scaler.acquireResultSurface(); | ||||
| 	} | ||||
|  | ||||
| 	logGlobal->debug("Loaded BMP font: '%s', height %d, ascent %d", | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
| #include "CursorHardware.h" | ||||
|  | ||||
| #include "SDL_Extensions.h" | ||||
| #include "SDLImageScaler.h" | ||||
|  | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
| @@ -60,7 +61,10 @@ void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivot | ||||
| 	CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY)); | ||||
|  | ||||
| 	image->draw(cursorSurface, Point(0,0), nullptr, GH.screenHandler().getScalingFactor()); | ||||
| 	auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y, EScalingAlgorithm::BILINEAR ); | ||||
|  | ||||
| 	SDLImageScaler scaler(cursorSurface); | ||||
| 	scaler.scaleSurface(cursorDimensionsScaled, EScalingAlgorithm::BILINEAR); | ||||
| 	SDL_Surface	* cursorSurfaceScaled = scaler.acquireResultSurface(); | ||||
|  | ||||
| 	auto oldCursor = cursor; | ||||
| 	cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y); | ||||
|   | ||||
| @@ -248,10 +248,11 @@ std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocato | ||||
| 		pathToLoad = *remappedLocator.image; | ||||
| 	} | ||||
|  | ||||
| 	if(!locator.image) | ||||
| 		return nullptr; | ||||
| 	if(locator.image) | ||||
| 		pathToLoad = *locator.image; | ||||
|  | ||||
| 	pathToLoad = *locator.image; | ||||
| 	if (pathToLoad.empty()) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	std::string imagePathString = pathToLoad.getName(); | ||||
|  | ||||
|   | ||||
| @@ -11,14 +11,12 @@ | ||||
| #include "SDLImage.h" | ||||
|  | ||||
| #include "SDLImageLoader.h" | ||||
| #include "SDLImageScaler.h" | ||||
| #include "SDL_Extensions.h" | ||||
|  | ||||
| #include "../render/ColorFilter.h" | ||||
| #include "../render/Colors.h" | ||||
| #include "../render/CBitmapHandler.h" | ||||
| #include "../render/CDefFile.h" | ||||
| #include "../render/Graphics.h" | ||||
| #include "../xBRZ/xbrz.h" | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../render/IScreenHandler.h" | ||||
|  | ||||
| @@ -145,82 +143,14 @@ void SDLImageShared::optimizeSurface() | ||||
| 	if (!surf) | ||||
| 		return; | ||||
|  | ||||
| 	int left = surf->w; | ||||
| 	int top = surf->h; | ||||
| 	int right = 0; | ||||
| 	int bottom = 0; | ||||
| 	SDLImageOptimizer optimizer(surf, Rect(margins, fullSize)); | ||||
|  | ||||
| 	// locate fully-transparent area around image | ||||
| 	// H3 hadles this on format level, but mods or images scaled in runtime do not | ||||
| 	if (surf->format->palette) | ||||
| 	{ | ||||
| 		for (int y = 0; y < surf->h; ++y) | ||||
| 		{ | ||||
| 			const uint8_t * row = static_cast<uint8_t *>(surf->pixels) + y * surf->pitch; | ||||
| 			for (int x = 0; x < surf->w; ++x) | ||||
| 			{ | ||||
| 				if (row[x] != 0) | ||||
| 				{ | ||||
| 					// opaque or can be opaque (e.g. disabled shadow) | ||||
| 					top = std::min(top, y); | ||||
| 					left = std::min(left, x); | ||||
| 					right = std::max(right, x); | ||||
| 					bottom = std::max(bottom, y); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		for (int y = 0; y < surf->h; ++y) | ||||
| 		{ | ||||
| 			for (int x = 0; x < surf->w; ++x) | ||||
| 			{ | ||||
| 				ColorRGBA color; | ||||
| 				SDL_GetRGBA(CSDL_Ext::getPixel(surf, x, y), surf->format, &color.r, &color.g, &color.b, &color.a); | ||||
| 	optimizer.optimizeSurface(surf); | ||||
| 	SDL_FreeSurface(surf); | ||||
|  | ||||
| 				if (color.a != SDL_ALPHA_TRANSPARENT) | ||||
| 				{ | ||||
| 					 // opaque | ||||
| 					top = std::min(top, y); | ||||
| 					left = std::min(left, x); | ||||
| 					right = std::max(right, x); | ||||
| 					bottom = std::max(bottom, y); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (left == surf->w) | ||||
| 	{ | ||||
| 		// empty image - simply delete it | ||||
| 		SDL_FreeSurface(surf); | ||||
| 		surf = nullptr; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (left != 0 || top != 0 || right != surf->w - 1 || bottom != surf->h - 1) | ||||
| 	{ | ||||
| 		// non-zero border found | ||||
| 		Rect newDimensions(left, top, right - left + 1, bottom - top + 1); | ||||
| 		SDL_Rect rectSDL = CSDL_Ext::toSDL(newDimensions); | ||||
| 		auto newSurface = CSDL_Ext::newSurface(newDimensions.dimensions(), surf); | ||||
| 		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE); | ||||
| 		SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr); | ||||
|  | ||||
| 		if (SDL_HasColorKey(surf)) | ||||
| 		{ | ||||
| 			uint32_t colorKey; | ||||
| 			SDL_GetColorKey(surf, &colorKey); | ||||
| 			SDL_SetColorKey(newSurface, SDL_TRUE, colorKey); | ||||
| 		} | ||||
|  | ||||
| 		SDL_FreeSurface(surf); | ||||
| 		surf = newSurface; | ||||
|  | ||||
| 		margins.x += left; | ||||
| 		margins.y += top; | ||||
| 	} | ||||
| 	surf = optimizer.acquireResultSurface(); | ||||
| 	margins = optimizer.getResultDimensions().topLeft(); | ||||
| 	fullSize = optimizer.getResultDimensions().dimensions(); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode mode) const | ||||
| @@ -234,22 +164,20 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL | ||||
| 	if (palette && surf->format->palette) | ||||
| 		SDL_SetSurfacePalette(surf, palette); | ||||
|  | ||||
| 	SDL_Surface * scaled = nullptr; | ||||
| 	SDLImageScaler scaler(surf, Rect(margins, fullSize)); | ||||
|  | ||||
| 	// dump heuristics to differentiate tileable UI elements from map object / combat assets | ||||
| 	if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE) | ||||
| 		scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_OPAQUE); | ||||
| 		scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), EScalingAlgorithm::XBRZ_OPAQUE); | ||||
| 	else | ||||
| 		scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_ALPHA); | ||||
| 		scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), EScalingAlgorithm::XBRZ_ALPHA); | ||||
|  | ||||
| 	SDL_Surface * scaled = scaler.acquireResultSurface(); | ||||
|  | ||||
| 	auto ret = std::make_shared<SDLImageShared>(scaled); | ||||
|  | ||||
| 	ret->fullSize.x = fullSize.x * factor; | ||||
| 	ret->fullSize.y = fullSize.y * factor; | ||||
|  | ||||
| 	ret->margins.x = (int) round((float)margins.x * factor); | ||||
| 	ret->margins.y = (int) round((float)margins.y * factor); | ||||
| 	ret->optimizeSurface(); | ||||
| 	ret->fullSize = scaler.getResultDimensions().dimensions(); | ||||
| 	ret->margins = scaler.getResultDimensions().topLeft(); | ||||
|  | ||||
| 	// erase our own reference | ||||
| 	SDL_FreeSurface(scaled); | ||||
| @@ -262,13 +190,14 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL | ||||
|  | ||||
| std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const | ||||
| { | ||||
| 	float scaleX = static_cast<float>(size.x) / fullSize.x; | ||||
| 	float scaleY = static_cast<float>(size.y) / fullSize.y; | ||||
|  | ||||
| 	if (palette && surf->format->palette) | ||||
| 		SDL_SetSurfacePalette(surf, palette); | ||||
|  | ||||
| 	auto scaled = CSDL_Ext::scaleSurface(surf, (int)(surf->w * scaleX), (int)(surf->h * scaleY), EScalingAlgorithm::BILINEAR); | ||||
| 	SDLImageScaler scaler(surf, Rect(margins, fullSize)); | ||||
|  | ||||
| 	scaler.scaleSurface(size, EScalingAlgorithm::XBRZ_ALPHA); | ||||
|  | ||||
| 	auto scaled = scaler.acquireResultSurface(); | ||||
|  | ||||
| 	if (scaled->format && scaled->format->palette) // fix color keying, because SDL loses it at this point | ||||
| 		CSDL_Ext::setColorKey(scaled, scaled->format->palette->colors[0]); | ||||
| @@ -278,12 +207,8 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, | ||||
| 		CSDL_Ext::setDefaultColorKey(scaled);//just in case | ||||
|  | ||||
| 	auto ret = std::make_shared<SDLImageShared>(scaled); | ||||
|  | ||||
| 	ret->fullSize.x = (int) round((float)fullSize.x * scaleX); | ||||
| 	ret->fullSize.y = (int) round((float)fullSize.y * scaleY); | ||||
|  | ||||
| 	ret->margins.x = (int) round((float)margins.x * scaleX); | ||||
| 	ret->margins.y = (int) round((float)margins.y * scaleY); | ||||
| 	ret->fullSize = scaler.getResultDimensions().dimensions(); | ||||
| 	ret->margins = scaler.getResultDimensions().topLeft(); | ||||
|  | ||||
| 	// erase our own reference | ||||
| 	SDL_FreeSurface(scaled); | ||||
|   | ||||
							
								
								
									
										233
									
								
								client/renderSDL/SDLImageScaler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								client/renderSDL/SDLImageScaler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | ||||
| /* | ||||
|  * SDLImageScaler.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "SDLImageScaler.h" | ||||
|  | ||||
| #include "SDL_Extensions.h" | ||||
|  | ||||
| #include "../CMT.h" | ||||
| #include "../xBRZ/xbrz.h" | ||||
|  | ||||
| #include <tbb/parallel_for.h> | ||||
| #include <SDL_surface.h> | ||||
|  | ||||
| SDLImageOptimizer::SDLImageOptimizer(SDL_Surface * surf, const Rect & virtualDimensions) | ||||
| 	: surf(surf) | ||||
| 	, virtualDimensions(virtualDimensions) | ||||
| { | ||||
| } | ||||
|  | ||||
| void SDLImageOptimizer::optimizeSurface(SDL_Surface * formatSourceSurface) | ||||
| { | ||||
| 	if (!surf) | ||||
| 		return; | ||||
|  | ||||
| 	int left = surf->w; | ||||
| 	int top = surf->h; | ||||
| 	int right = 0; | ||||
| 	int bottom = 0; | ||||
|  | ||||
| 	// locate fully-transparent area around image | ||||
| 	// H3 hadles this on format level, but mods or images scaled in runtime do not | ||||
| 	if (surf->format->palette) | ||||
| 	{ | ||||
| 		for (int y = 0; y < surf->h; ++y) | ||||
| 		{ | ||||
| 			const uint8_t * row = static_cast<uint8_t *>(surf->pixels) + y * surf->pitch; | ||||
| 			for (int x = 0; x < surf->w; ++x) | ||||
| 			{ | ||||
| 				if (row[x] != 0) | ||||
| 				{ | ||||
| 					// opaque or can be opaque (e.g. disabled shadow) | ||||
| 					top = std::min(top, y); | ||||
| 					left = std::min(left, x); | ||||
| 					right = std::max(right, x); | ||||
| 					bottom = std::max(bottom, y); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		for (int y = 0; y < surf->h; ++y) | ||||
| 		{ | ||||
| 			for (int x = 0; x < surf->w; ++x) | ||||
| 			{ | ||||
| 				ColorRGBA color; | ||||
| 				SDL_GetRGBA(CSDL_Ext::getPixel(surf, x, y), surf->format, &color.r, &color.g, &color.b, &color.a); | ||||
|  | ||||
| 				if (color.a != SDL_ALPHA_TRANSPARENT) | ||||
| 				{ | ||||
| 					// opaque | ||||
| 					top = std::min(top, y); | ||||
| 					left = std::min(left, x); | ||||
| 					right = std::max(right, x); | ||||
| 					bottom = std::max(bottom, y); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// empty image | ||||
| 	if (left == surf->w) | ||||
| 		return; | ||||
|  | ||||
| 	if (left != 0 || top != 0 || right != surf->w - 1 || bottom != surf->h - 1) | ||||
| 	{ | ||||
| 		// non-zero border found | ||||
| 		Rect newDimensions(left, top, right - left + 1, bottom - top + 1); | ||||
| 		SDL_Rect rectSDL = CSDL_Ext::toSDL(newDimensions); | ||||
| 		auto newSurface = CSDL_Ext::newSurface(newDimensions.dimensions(), formatSourceSurface); | ||||
| 		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE); | ||||
| 		SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr); | ||||
|  | ||||
| 		if (SDL_HasColorKey(surf)) | ||||
| 		{ | ||||
| 			uint32_t colorKey; | ||||
| 			SDL_GetColorKey(surf, &colorKey); | ||||
| 			SDL_SetColorKey(newSurface, SDL_TRUE, colorKey); | ||||
| 		} | ||||
| 		output = newSurface; | ||||
|  | ||||
| 		virtualDimensions.x += left; | ||||
| 		virtualDimensions.y += top; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		output = surf; | ||||
| 		output->refcount += 1; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| SDL_Surface * SDLImageOptimizer::acquireResultSurface() | ||||
| { | ||||
| 	SDL_Surface * result = output; | ||||
| 	output = nullptr; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| const Rect & SDLImageOptimizer::getResultDimensions() const | ||||
| { | ||||
| 	return virtualDimensions; | ||||
| } | ||||
|  | ||||
| void SDLImageScaler::scaleSurface(Point targetDimensions, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	if(!targetDimensions.x || !targetDimensions.y) | ||||
| 		throw std::runtime_error("invalid scaling dimensions!"); | ||||
|  | ||||
| 	Point inputSurfaceSize(intermediate->w, intermediate->h); | ||||
| 	Point outputSurfaceSize = targetDimensions * inputSurfaceSize / virtualDimensionsInput.dimensions(); | ||||
| 	Point outputMargins = targetDimensions * virtualDimensionsInput.topLeft() / virtualDimensionsInput.dimensions(); | ||||
|  | ||||
| 	// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%? | ||||
| 	// Need to investigate which is optimal	for performance and for visuals | ||||
| 	ret = CSDL_Ext::newSurface(Point(outputSurfaceSize.x, outputSurfaceSize.y), intermediate); | ||||
|  | ||||
| 	virtualDimensionsOutput = Rect(outputMargins, targetDimensions); // TODO: account for input virtual size | ||||
|  | ||||
| 	const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels); | ||||
| 	uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels); | ||||
|  | ||||
| 	if (algorithm == EScalingAlgorithm::NEAREST) | ||||
| 		xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 	else | ||||
| 		xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| } | ||||
|  | ||||
| void SDLImageScaler::scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	if(factor == 0) | ||||
| 		throw std::runtime_error("invalid scaling factor!"); | ||||
|  | ||||
| 	int newWidth = intermediate->w * factor; | ||||
| 	int newHight = intermediate->h * factor; | ||||
|  | ||||
| 	virtualDimensionsOutput = virtualDimensionsInput * factor; | ||||
|  | ||||
| 	ret = CSDL_Ext::newSurface(Point(newWidth, newHight), intermediate); | ||||
|  | ||||
| 	assert(intermediate->pitch == intermediate->w * 4); | ||||
| 	assert(ret->pitch == ret->w * 4); | ||||
|  | ||||
| 	const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels); | ||||
| 	uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels); | ||||
|  | ||||
| 	switch (algorithm) | ||||
| 	{ | ||||
| 		case EScalingAlgorithm::NEAREST: | ||||
| 			xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 			break; | ||||
| 		case EScalingAlgorithm::BILINEAR: | ||||
| 			xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 			break; | ||||
| 		case EScalingAlgorithm::XBRZ_ALPHA: | ||||
| 		case EScalingAlgorithm::XBRZ_OPAQUE: | ||||
| 		{ | ||||
| 			auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB; | ||||
|  | ||||
| 			if(intermediate->h < 32) | ||||
| 			{ | ||||
| 				// for tiny images tbb incurs too high overhead | ||||
| 				xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// xbrz recommends granulation of 16, but according to tests, for smaller images granulation of 4 is actually the best option | ||||
| 				const int granulation = intermediate->h > 400 ? 16 : 4; | ||||
| 				tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [this, factor, srcPixels, dstPixels, format](const tbb::blocked_range<size_t> & r) | ||||
| 								  { | ||||
| 									  xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end()); | ||||
| 								  }); | ||||
| 			} | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 		default: | ||||
| 			throw std::runtime_error("invalid scaling algorithm!"); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| SDLImageScaler::SDLImageScaler(SDL_Surface * surf) | ||||
| 	:SDLImageScaler(surf, Rect(0,0,surf->w, surf->h)) | ||||
| { | ||||
| } | ||||
|  | ||||
| SDLImageScaler::SDLImageScaler(SDL_Surface * surf, const Rect & virtualDimensions) | ||||
| { | ||||
| 	SDLImageOptimizer optimizer(surf, virtualDimensions); | ||||
| 	optimizer.optimizeSurface(screen); | ||||
| 	intermediate = optimizer.acquireResultSurface(); | ||||
| 	virtualDimensionsInput = optimizer.getResultDimensions(); | ||||
|  | ||||
| 	if (intermediate == surf) | ||||
| 	{ | ||||
| 		SDL_FreeSurface(intermediate); | ||||
| 		intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| SDLImageScaler::~SDLImageScaler() | ||||
| { | ||||
| 	SDL_FreeSurface(intermediate); | ||||
| 	SDL_FreeSurface(ret); | ||||
| } | ||||
|  | ||||
| SDL_Surface * SDLImageScaler::acquireResultSurface() | ||||
| { | ||||
| 	SDL_Surface * result = ret; | ||||
| 	ret = nullptr; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| const Rect & SDLImageScaler::getResultDimensions() const | ||||
| { | ||||
| 	return virtualDimensionsOutput; | ||||
| } | ||||
							
								
								
									
										63
									
								
								client/renderSDL/SDLImageScaler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								client/renderSDL/SDLImageScaler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| /* | ||||
|  * SDLImageScaler.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "../render/IImage.h" | ||||
| #include "../../lib/Rect.h" | ||||
|  | ||||
| class SDLImageOptimizer : boost::noncopyable | ||||
| { | ||||
| 	SDL_Surface * surf = nullptr; | ||||
| 	SDL_Surface * output = nullptr; | ||||
| 	Rect virtualDimensions = Rect(0,0,0,0); | ||||
| public: | ||||
| 	SDLImageOptimizer(SDL_Surface * surf, const Rect & virtualDimensions); | ||||
|  | ||||
| 	void optimizeSurface(SDL_Surface * formatSourceSurface); | ||||
|  | ||||
| 	/// Aquires resulting surface and transfers surface ownership to the caller | ||||
| 	/// May return nullptr if input image was empty | ||||
| 	SDL_Surface * acquireResultSurface(); | ||||
|  | ||||
| 	/// Returns adjusted virtual dimensions of resulting surface | ||||
| 	const Rect & getResultDimensions() const; | ||||
| }; | ||||
|  | ||||
| /// Class that performs scaling of SDL surfaces | ||||
| /// Object construction MUST be performed while holding UI lock | ||||
| /// but task execution can be performed asynchronously if needed | ||||
| class SDLImageScaler : boost::noncopyable | ||||
| { | ||||
| 	SDL_Surface * intermediate = nullptr; | ||||
| 	SDL_Surface * ret = nullptr; | ||||
| 	Rect virtualDimensionsInput = Rect(0,0,0,0); | ||||
| 	Rect virtualDimensionsOutput = Rect(0,0,0,0); | ||||
|  | ||||
| public: | ||||
| 	SDLImageScaler(SDL_Surface * surf); | ||||
| 	SDLImageScaler(SDL_Surface * surf, const Rect & virtualDimensions); | ||||
| 	~SDLImageScaler(); | ||||
|  | ||||
| 	/// Performs upscaling or downscaling to a requested dimensions | ||||
| 	/// Aspect ratio is NOT maintained. | ||||
| 	/// xbrz algorithm is not supported in this mode | ||||
| 	void scaleSurface(Point dimensions, EScalingAlgorithm algorithm); | ||||
|  | ||||
| 	/// Performs upscaling by specified integral factor, potentially using xbrz algorithm if requested | ||||
| 	void scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm); | ||||
|  | ||||
| 	/// Aquires resulting surface and transfers surface ownership to the caller | ||||
| 	/// May return nullptr if input image was empty | ||||
| 	SDL_Surface * acquireResultSurface(); | ||||
|  | ||||
| 	/// Returns adjusted virtual dimensions of resulting surface | ||||
| 	const Rect & getResultDimensions() const; | ||||
| }; | ||||
| @@ -631,89 +631,6 @@ void CSDL_Ext::convertToGrayscale( SDL_Surface * surf, const Rect & rect ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // scaling via bilinear interpolation algorithm. | ||||
| // NOTE: best results are for scaling in range 50%...200%. | ||||
| // And upscaling looks awful right now - should be fixed somehow | ||||
| SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	if(!surf || !width || !height) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%? | ||||
| 	// Need to investigate which is optimal	for performance and for visuals | ||||
|  | ||||
| 	SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0); | ||||
| 	SDL_Surface * ret = newSurface(Point(width, height), intermediate); | ||||
|  | ||||
| 	const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels); | ||||
| 	uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels); | ||||
|  | ||||
| 	if (algorithm == EScalingAlgorithm::NEAREST) | ||||
| 		xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 	else | ||||
| 		xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
|  | ||||
| 	SDL_FreeSurface(intermediate); | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm algorithm) | ||||
| { | ||||
| 	if(surf == nullptr || factor == 0) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	int newWidth = surf->w * factor; | ||||
| 	int newHight = surf->h * factor; | ||||
|  | ||||
| 	SDL_Surface * intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0); | ||||
| 	SDL_Surface * ret = newSurface(Point(newWidth, newHight), intermediate); | ||||
|  | ||||
| 	assert(intermediate->pitch == intermediate->w * 4); | ||||
| 	assert(ret->pitch == ret->w * 4); | ||||
|  | ||||
| 	const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels); | ||||
| 	uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels); | ||||
|  | ||||
| 	switch (algorithm) | ||||
| 	{ | ||||
| 		case EScalingAlgorithm::NEAREST: | ||||
| 			xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 			break; | ||||
| 		case EScalingAlgorithm::BILINEAR: | ||||
| 			xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); | ||||
| 			break; | ||||
| 		case EScalingAlgorithm::XBRZ_ALPHA: | ||||
| 		case EScalingAlgorithm::XBRZ_OPAQUE: | ||||
| 		{ | ||||
| 			auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB; | ||||
|  | ||||
| 			if(intermediate->h < 32) | ||||
| 			{ | ||||
| 				// for tiny images tbb incurs too high overhead | ||||
| 				xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// xbrz recommends granulation of 16, but according to tests, for smaller images granulation of 4 is actually the best option | ||||
| 				const int granulation = intermediate->h > 400 ? 16 : 4; | ||||
| 				tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate, format](const tbb::blocked_range<size_t> & r) | ||||
| 				{ | ||||
| 					xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end()); | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 		default: | ||||
| 			throw std::runtime_error("invalid scaling algorithm!"); | ||||
| 	} | ||||
|  | ||||
| 	SDL_FreeSurface(intermediate); | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| void CSDL_Ext::blitSurface(SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPoint) | ||||
| { | ||||
| 	SDL_Rect srcRect = CSDL_Ext::toSDL(srcRectInput); | ||||
|   | ||||
| @@ -18,7 +18,6 @@ struct SDL_Renderer; | ||||
| struct SDL_Texture; | ||||
| struct SDL_Surface; | ||||
| struct SDL_Color; | ||||
| enum class EScalingAlgorithm : int8_t; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| @@ -91,10 +90,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &, | ||||
| 	template<int bpp> | ||||
| 	SDL_Surface * createSurfaceWithBpp(int width, int height); //create surface with give bits per pixels value | ||||
|  | ||||
| 	// bilinear filtering. Always returns rgba surface | ||||
| 	SDL_Surface * scaleSurface(SDL_Surface * surf, int width, int height, EScalingAlgorithm scaler); | ||||
| 	SDL_Surface * scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm scaler); | ||||
|  | ||||
| 	template<int bpp> | ||||
| 	void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect); | ||||
| 	void convertToGrayscale(SDL_Surface * surf, const Rect & rect); | ||||
|   | ||||
| @@ -260,7 +260,7 @@ void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Re | ||||
| 		if (scaled.at(scalingFactor).shadow.at(0)) | ||||
| 			flipAndDraw(scaled.at(scalingFactor).shadow, Colors::WHITE_TRUE, parameters.alphaValue); | ||||
|  | ||||
| 		if (parameters.player != PlayerColor::CANNOT_DETERMINE) | ||||
| 		if (parameters.player.isValidPlayer()) | ||||
| 		{ | ||||
| 			scaled.at(scalingFactor).playerColored[parameters.player.getNum()]->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer); | ||||
| 		} | ||||
| @@ -351,7 +351,7 @@ void ScalableImageInstance::playerColored(const PlayerColor & player) | ||||
| { | ||||
| 	parameters.player = player; | ||||
|  | ||||
| 	if (!parameters.palette) | ||||
| 	if (parameters.palette) | ||||
| 		parameters.playerColored(player); | ||||
|  | ||||
| 	image->preparePlayerColoredImage(player); | ||||
|   | ||||
| @@ -15,9 +15,6 @@ | ||||
|  | ||||
| #include "../../lib/Color.h" | ||||
|  | ||||
| #include <tbb/concurrent_queue.h> | ||||
| #include <tbb/task_arena.h> | ||||
|  | ||||
| struct SDL_Palette; | ||||
|  | ||||
| class ScalableImageInstance; | ||||
| @@ -47,24 +44,6 @@ struct ScalableImageParameters : boost::noncopyable | ||||
| 	void adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask); | ||||
| }; | ||||
|  | ||||
| class ImageScaler | ||||
| { | ||||
| 	ImageScaler(); | ||||
|  | ||||
| 	tbb::task_arena arena; | ||||
| public: | ||||
| 	static ImageScaler & getInstance() | ||||
| 	{ | ||||
| 		static ImageScaler scaler; | ||||
| 		return scaler; | ||||
| 	} | ||||
|  | ||||
| 	void enqueueTask(const std::function<void()> & task) | ||||
| 	{ | ||||
| 		arena.enqueue(task); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| class ScalableImageShared final : public std::enable_shared_from_this<ScalableImageShared>, boost::noncopyable | ||||
| { | ||||
| 	static constexpr int scalingSize = 5; // 0-4 range. TODO: switch to either 2-4 or 1-4 | ||||
| @@ -97,12 +76,6 @@ class ScalableImageShared final : public std::enable_shared_from_this<ScalableIm | ||||
| 	/// Locator of this image, for loading additional (e.g. upscaled) images | ||||
| 	const SharedImageLocator locator; | ||||
|  | ||||
| 	/// Contains all upscaling tasks related to this image that finished processing and can be applied | ||||
| 	tbb::concurrent_queue<std::function<void()>> upscalingQueue; | ||||
|  | ||||
| 	/// Number of images that are currently being upscaled | ||||
| 	int scheduledUpscalingEvents = 0; | ||||
|  | ||||
| 	std::shared_ptr<const ISharedImage> loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color) const; | ||||
|  | ||||
| 	void loadScaledImages(int8_t scalingFactor, PlayerColor color); | ||||
|   | ||||
| @@ -54,6 +54,11 @@ public: | ||||
| 		return Point(x*mul, y*mul); | ||||
| 	} | ||||
|  | ||||
| 	constexpr Point operator/(const Point &b) const | ||||
| 	{ | ||||
| 		return Point(x/b.x,y/b.y); | ||||
| 	} | ||||
|  | ||||
| 	constexpr Point operator*(const Point &b) const | ||||
| 	{ | ||||
| 		return Point(x*b.x,y*b.y); | ||||
|   | ||||
| @@ -120,7 +120,7 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i | ||||
| 	{ | ||||
| 		static const CSelector selector = Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT); | ||||
| 		const auto & bonuses = sharedCache->roughTerrainDiscount.getBonusList(target, selector); | ||||
| 		roughTerrainDiscountValue = bonuses->getFirst(daySelector) != nullptr; | ||||
| 		roughTerrainDiscountValue = bonuses->valOfBonuses(daySelector); | ||||
| 	} | ||||
|  | ||||
| 	{ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user