ScalableImage.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /*
  2. * ScalableImage.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "ScalableImage.h"
  12. #include "SDLImage.h"
  13. #include "SDL_Extensions.h"
  14. #include "../gui/CGuiHandler.h"
  15. #include "../render/ColorFilter.h"
  16. #include "../render/Colors.h"
  17. #include "../render/Graphics.h"
  18. #include "../render/IRenderHandler.h"
  19. #include "../render/IScreenHandler.h"
  20. #include "../../lib/constants/EntityIdentifiers.h"
  21. #include <SDL_surface.h>
  22. //First 8 colors in def palette used for transparency
  23. static constexpr std::array<SDL_Color, 8> sourcePalette = {{
  24. {0, 255, 255, SDL_ALPHA_OPAQUE},
  25. {255, 150, 255, SDL_ALPHA_OPAQUE},
  26. {255, 100, 255, SDL_ALPHA_OPAQUE},
  27. {255, 50, 255, SDL_ALPHA_OPAQUE},
  28. {255, 0, 255, SDL_ALPHA_OPAQUE},
  29. {255, 255, 0, SDL_ALPHA_OPAQUE},
  30. {180, 0, 255, SDL_ALPHA_OPAQUE},
  31. {0, 255, 0, SDL_ALPHA_OPAQUE}
  32. }};
  33. static constexpr std::array<ColorRGBA, 8> targetPalette = {{
  34. {0, 0, 0, 0 }, // 0 - transparency ( used in most images )
  35. {0, 0, 0, 64 }, // 1 - shadow border ( used in battle, adventure map def's )
  36. {0, 0, 0, 64 }, // 2 - shadow border ( used in fog-of-war def's )
  37. {0, 0, 0, 128}, // 3 - shadow body ( used in fog-of-war def's )
  38. {0, 0, 0, 128}, // 4 - shadow body ( used in battle, adventure map def's )
  39. {0, 0, 0, 0 }, // 5 - selection / owner flag ( used in battle, adventure map def's )
  40. {0, 0, 0, 128}, // 6 - shadow body below selection ( used in battle def's )
  41. {0, 0, 0, 64 } // 7 - shadow border below selection ( used in battle def's )
  42. }};
  43. static ui8 mixChannels(ui8 c1, ui8 c2, ui8 a1, ui8 a2)
  44. {
  45. return c1*a1 / 256 + c2*a2*(255 - a1) / 256 / 256;
  46. }
  47. static ColorRGBA addColors(const ColorRGBA & base, const ColorRGBA & over)
  48. {
  49. return ColorRGBA(
  50. mixChannels(over.r, base.r, over.a, base.a),
  51. mixChannels(over.g, base.g, over.a, base.a),
  52. mixChannels(over.b, base.b, over.a, base.a),
  53. static_cast<ui8>(over.a + base.a * (255 - over.a) / 256)
  54. );
  55. }
  56. static bool colorsSimilar (const SDL_Color & lhs, const SDL_Color & rhs)
  57. {
  58. // it seems that H3 does not requires exact match to replace colors -> (255, 103, 255) gets interpreted as shadow
  59. // exact logic is not clear and requires extensive testing with image editing
  60. // potential reason is that H3 uses 16-bit color format (565 RGB bits), meaning that 3 least significant bits are lost in red and blue component
  61. static const int threshold = 8;
  62. int diffR = static_cast<int>(lhs.r) - rhs.r;
  63. int diffG = static_cast<int>(lhs.g) - rhs.g;
  64. int diffB = static_cast<int>(lhs.b) - rhs.b;
  65. int diffA = static_cast<int>(lhs.a) - rhs.a;
  66. return std::abs(diffR) < threshold && std::abs(diffG) < threshold && std::abs(diffB) < threshold && std::abs(diffA) < threshold;
  67. }
  68. ScalableImageParameters::ScalableImageParameters(const SDL_Palette * originalPalette, EImageBlitMode blitMode)
  69. {
  70. if (originalPalette)
  71. {
  72. palette = SDL_AllocPalette(originalPalette->ncolors);
  73. SDL_SetPaletteColors(palette, originalPalette->colors, 0, originalPalette->ncolors);
  74. preparePalette(originalPalette, blitMode);
  75. }
  76. }
  77. ScalableImageParameters::~ScalableImageParameters()
  78. {
  79. SDL_FreePalette(palette);
  80. }
  81. void ScalableImageParameters::preparePalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode)
  82. {
  83. switch(blitMode)
  84. {
  85. case EImageBlitMode::ONLY_SHADOW:
  86. case EImageBlitMode::ONLY_OVERLAY:
  87. adjustPalette(originalPalette, blitMode, ColorFilter::genAlphaShifter(0), 0);
  88. break;
  89. }
  90. switch(blitMode)
  91. {
  92. case EImageBlitMode::SIMPLE:
  93. case EImageBlitMode::WITH_SHADOW:
  94. case EImageBlitMode::ONLY_SHADOW:
  95. case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
  96. setShadowTransparency(originalPalette, 1.0);
  97. break;
  98. case EImageBlitMode::ONLY_BODY:
  99. case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
  100. case EImageBlitMode::ONLY_OVERLAY:
  101. setShadowTransparency(originalPalette, 0.0);
  102. break;
  103. }
  104. switch(blitMode)
  105. {
  106. case EImageBlitMode::ONLY_OVERLAY:
  107. case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
  108. setOverlayColor(originalPalette, Colors::WHITE_TRUE);
  109. break;
  110. case EImageBlitMode::ONLY_SHADOW:
  111. case EImageBlitMode::ONLY_BODY:
  112. setOverlayColor(originalPalette, Colors::TRANSPARENCY);
  113. break;
  114. }
  115. }
  116. void ScalableImageParameters::setOverlayColor(const SDL_Palette * originalPalette, const ColorRGBA & color)
  117. {
  118. palette->colors[5] = CSDL_Ext::toSDL(addColors(targetPalette[5], color));
  119. for (int i : {6,7})
  120. {
  121. if (colorsSimilar(originalPalette->colors[i], sourcePalette[i]))
  122. palette->colors[i] = CSDL_Ext::toSDL(addColors(targetPalette[i], color));
  123. }
  124. }
  125. void ScalableImageParameters::shiftPalette(const SDL_Palette * originalPalette, uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
  126. {
  127. std::vector<SDL_Color> shifterColors(colorsToMove);
  128. for(uint32_t i=0; i<colorsToMove; ++i)
  129. shifterColors[(i+distanceToMove)%colorsToMove] = originalPalette->colors[firstColorID + i];
  130. SDL_SetPaletteColors(palette, shifterColors.data(), firstColorID, colorsToMove);
  131. }
  132. void ScalableImageParameters::setShadowTransparency(const SDL_Palette * originalPalette, float factor)
  133. {
  134. ColorRGBA shadow50(0, 0, 0, 128 * factor);
  135. ColorRGBA shadow25(0, 0, 0, 64 * factor);
  136. std::array<SDL_Color, 5> colorsSDL = {
  137. originalPalette->colors[0],
  138. originalPalette->colors[1],
  139. originalPalette->colors[2],
  140. originalPalette->colors[3],
  141. originalPalette->colors[4]
  142. };
  143. // seems to be used unconditionally
  144. colorsSDL[0] = CSDL_Ext::toSDL(Colors::TRANSPARENCY);
  145. colorsSDL[1] = CSDL_Ext::toSDL(shadow25);
  146. colorsSDL[4] = CSDL_Ext::toSDL(shadow50);
  147. // seems to be used only if color matches
  148. if (colorsSimilar(originalPalette->colors[2], sourcePalette[2]))
  149. colorsSDL[2] = CSDL_Ext::toSDL(shadow25);
  150. if (colorsSimilar(originalPalette->colors[3], sourcePalette[3]))
  151. colorsSDL[3] = CSDL_Ext::toSDL(shadow50);
  152. SDL_SetPaletteColors(palette, colorsSDL.data(), 0, colorsSDL.size());
  153. }
  154. void ScalableImageParameters::adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask)
  155. {
  156. // If shadow is enabled, following colors must be skipped unconditionally
  157. if (blitMode == EImageBlitMode::WITH_SHADOW || blitMode == EImageBlitMode::WITH_SHADOW_AND_OVERLAY)
  158. colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4);
  159. // Note: here we skip first colors in the palette that are predefined in H3 images
  160. for(int i = 0; i < palette->ncolors; i++)
  161. {
  162. if (i < std::size(sourcePalette) && colorsSimilar(sourcePalette[i], originalPalette->colors[i]))
  163. continue;
  164. if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1)
  165. continue;
  166. palette->colors[i] = CSDL_Ext::toSDL(shifter.shiftColor(CSDL_Ext::fromSDL(originalPalette->colors[i])));
  167. }
  168. }
  169. ScalableImageShared::ScalableImageShared(const SharedImageLocator & locator, const std::shared_ptr<const ISharedImage> & baseImage)
  170. :locator(locator)
  171. {
  172. base[0] = baseImage;
  173. assert(base[0] != nullptr);
  174. loadScaledImages(GH.screenHandler().getScalingFactor(), PlayerColor::CANNOT_DETERMINE);
  175. }
  176. Point ScalableImageShared::dimensions() const
  177. {
  178. return base[0]->dimensions();
  179. }
  180. void ScalableImageShared::exportBitmap(const boost::filesystem::path & path, const ScalableImageParameters & parameters) const
  181. {
  182. base[0]->exportBitmap(path, parameters.palette);
  183. }
  184. bool ScalableImageShared::isTransparent(const Point & coords) const
  185. {
  186. return base[0]->isTransparent(coords);
  187. }
  188. Rect ScalableImageShared::contentRect() const
  189. {
  190. return base[0]->contentRect();
  191. }
  192. void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Rect * src, const ScalableImageParameters & parameters, int scalingFactor)
  193. {
  194. const auto & flipAndDraw = [&](FlippedImages & images, const ColorRGBA & colorMultiplier, uint8_t alphaValue){
  195. int index = 0;
  196. if (parameters.flipVertical)
  197. {
  198. if (!images[index|1])
  199. images[index|1] = images[index]->verticalFlip();
  200. index |= 1;
  201. }
  202. if (parameters.flipHorizontal)
  203. {
  204. if (!images[index|2])
  205. images[index|2] = images[index]->horizontalFlip();
  206. index |= 2;
  207. }
  208. images[index]->draw(where, parameters.palette, dest, src, colorMultiplier, alphaValue, locator.layer);
  209. };
  210. if (scalingFactor == 1)
  211. {
  212. flipAndDraw(base, parameters.colorMultiplier, parameters.alphaValue);
  213. }
  214. else
  215. {
  216. if (scaled.at(scalingFactor).shadow.at(0))
  217. flipAndDraw(scaled.at(scalingFactor).shadow, Colors::WHITE_TRUE, parameters.alphaValue);
  218. if (parameters.player != PlayerColor::CANNOT_DETERMINE)
  219. {
  220. scaled.at(scalingFactor).playerColored[parameters.player.getNum()]->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer);
  221. }
  222. else
  223. {
  224. if (scaled.at(scalingFactor).body.at(0))
  225. flipAndDraw(scaled.at(scalingFactor).body, parameters.colorMultiplier, parameters.alphaValue);
  226. }
  227. if (scaled.at(scalingFactor).overlay.at(0))
  228. flipAndDraw(scaled.at(scalingFactor).overlay, parameters.ovelayColorMultiplier, static_cast<int>(parameters.alphaValue) * parameters.ovelayColorMultiplier.a / 255);
  229. }
  230. }
  231. const SDL_Palette * ScalableImageShared::getPalette() const
  232. {
  233. return base[0]->getPalette();
  234. }
  235. std::shared_ptr<ScalableImageInstance> ScalableImageShared::createImageReference()
  236. {
  237. return std::make_shared<ScalableImageInstance>(shared_from_this(), locator.layer);
  238. }
  239. ScalableImageInstance::ScalableImageInstance(const std::shared_ptr<ScalableImageShared> & image, EImageBlitMode blitMode)
  240. :image(image)
  241. ,parameters(image->getPalette(), blitMode)
  242. ,blitMode(blitMode)
  243. {
  244. assert(image);
  245. }
  246. void ScalableImageInstance::scaleTo(const Point & size)
  247. {
  248. assert(0);
  249. }
  250. void ScalableImageInstance::exportBitmap(const boost::filesystem::path & path) const
  251. {
  252. image->exportBitmap(path, parameters);
  253. }
  254. bool ScalableImageInstance::isTransparent(const Point & coords) const
  255. {
  256. return image->isTransparent(coords);
  257. }
  258. Rect ScalableImageInstance::contentRect() const
  259. {
  260. return image->contentRect();
  261. }
  262. Point ScalableImageInstance::dimensions() const
  263. {
  264. return image->dimensions();
  265. }
  266. void ScalableImageInstance::setAlpha(uint8_t value)
  267. {
  268. parameters.alphaValue = value;
  269. }
  270. void ScalableImageInstance::draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const
  271. {
  272. image->draw(where, pos, src, parameters, scalingFactor);
  273. }
  274. void ScalableImageInstance::setOverlayColor(const ColorRGBA & color)
  275. {
  276. parameters.ovelayColorMultiplier = color;
  277. if (parameters.palette)
  278. parameters.setOverlayColor(image->getPalette(), color);
  279. }
  280. void ScalableImageInstance::playerColored(PlayerColor player)
  281. {
  282. parameters.player = player;
  283. if (!parameters.palette)
  284. parameters.playerColored(player);
  285. image->preparePlayerColoredImage(player);
  286. }
  287. void ScalableImageParameters::playerColored(PlayerColor player)
  288. {
  289. graphics->setPlayerPalette(palette, player);
  290. }
  291. void ScalableImageInstance::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
  292. {
  293. if (parameters.palette)
  294. parameters.shiftPalette(image->getPalette(),firstColorID, colorsToMove, distanceToMove);
  295. }
  296. void ScalableImageInstance::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
  297. {
  298. if (parameters.palette)
  299. parameters.adjustPalette(image->getPalette(), blitMode, shifter, colorsToSkipMask);
  300. }
  301. void ScalableImageInstance::horizontalFlip()
  302. {
  303. parameters.flipHorizontal = !parameters.flipHorizontal;
  304. }
  305. void ScalableImageInstance::verticalFlip()
  306. {
  307. parameters.flipVertical = !parameters.flipVertical;
  308. }
  309. std::shared_ptr<const ISharedImage> ScalableImageShared::loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color) const
  310. {
  311. ImageLocator loadingLocator;
  312. loadingLocator.image = locator.image;
  313. loadingLocator.defFile = locator.defFile;
  314. loadingLocator.defFrame = locator.defFrame;
  315. loadingLocator.defGroup = locator.defGroup;
  316. loadingLocator.layer = mode;
  317. loadingLocator.scalingFactor = scalingFactor;
  318. loadingLocator.playerColored = color;
  319. // best case - requested image is already available in filesystem
  320. auto loadedImage = GH.renderHandler().loadSingleImage(loadingLocator);
  321. if (loadedImage)
  322. return loadedImage;
  323. // alternatively, find largest pre-scaled image, load it and rescale to desired scaling
  324. Point targetSize = base[0]->dimensions() * scalingFactor;
  325. for (int8_t scaling = 4; scaling > 1; --scaling)
  326. {
  327. loadingLocator.scalingFactor = scaling;
  328. auto loadedImage = GH.renderHandler().loadSingleImage(loadingLocator);
  329. if (loadedImage)
  330. return loadedImage->scaleTo(targetSize, nullptr);
  331. }
  332. // if all else fails - use base (presumably, indexed) image and convert it to desired form
  333. ScalableImageParameters parameters(getPalette(), mode);
  334. if (color != PlayerColor::CANNOT_DETERMINE)
  335. parameters.playerColored(color);
  336. return base[0]->scaleInteger(scalingFactor, parameters.palette, mode);
  337. }
  338. void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor color)
  339. {
  340. if (scalingFactor == 1)
  341. return; // no op. TODO: consider loading 1x images for mods, as alternative to palette-based animations
  342. if (scaled[scalingFactor].body[0] == nullptr)
  343. {
  344. switch(locator.layer)
  345. {
  346. case EImageBlitMode::OPAQUE:
  347. case EImageBlitMode::COLORKEY:
  348. case EImageBlitMode::SIMPLE:
  349. scaled[scalingFactor].body[0] = loadOrGenerateImage(locator.layer, scalingFactor, PlayerColor::CANNOT_DETERMINE);
  350. break;
  351. case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
  352. case EImageBlitMode::ONLY_BODY:
  353. scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, PlayerColor::CANNOT_DETERMINE);
  354. break;
  355. case EImageBlitMode::WITH_SHADOW:
  356. case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
  357. scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE);
  358. break;
  359. }
  360. }
  361. if (color != PlayerColor::CANNOT_DETERMINE && scaled[scalingFactor].playerColored[color.getNum()] == nullptr)
  362. {
  363. switch(locator.layer)
  364. {
  365. case EImageBlitMode::OPAQUE:
  366. case EImageBlitMode::COLORKEY:
  367. case EImageBlitMode::SIMPLE:
  368. scaled[scalingFactor].playerColored[color.getNum()] = loadOrGenerateImage(locator.layer, scalingFactor, color);
  369. break;
  370. case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
  371. case EImageBlitMode::ONLY_BODY:
  372. scaled[scalingFactor].playerColored[color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, color);
  373. break;
  374. case EImageBlitMode::WITH_SHADOW:
  375. case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
  376. scaled[scalingFactor].playerColored[color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, color);
  377. break;
  378. }
  379. }
  380. if (scaled[scalingFactor].shadow[0] == nullptr)
  381. {
  382. switch(locator.layer)
  383. {
  384. case EImageBlitMode::WITH_SHADOW:
  385. case EImageBlitMode::ONLY_SHADOW:
  386. case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
  387. scaled[scalingFactor].shadow[0] = loadOrGenerateImage(EImageBlitMode::ONLY_SHADOW, scalingFactor, PlayerColor::CANNOT_DETERMINE);
  388. break;
  389. default:
  390. break;
  391. }
  392. }
  393. if (scaled[scalingFactor].overlay[0] == nullptr)
  394. {
  395. switch(locator.layer)
  396. {
  397. case EImageBlitMode::ONLY_OVERLAY:
  398. case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
  399. scaled[scalingFactor].overlay[0] = loadOrGenerateImage(EImageBlitMode::ONLY_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE);
  400. break;
  401. default:
  402. break;
  403. }
  404. }
  405. }
  406. void ScalableImageShared::preparePlayerColoredImage(PlayerColor color)
  407. {
  408. loadScaledImages(GH.screenHandler().getScalingFactor(), color);
  409. }