Worldstone
 All Classes Files Functions Variables Enumerations Enumerator Macros Pages
FileBrowser.cpp
1 #include "FileBrowser.h"
2 #include <SystemUtils.h>
3 #include <cof.h>
4 #include <dc6.h>
5 #include <dcc.h>
6 #include <fmt/format.h>
7 #include "DrawSprite.h"
8 #include "bx/debug.h"
9 #include "imgui/imgui_bgfx.h"
10 
11 // The following should probably go to a PlatformIncludes.h
12 #ifdef WS_PLATFORM_WINDOWS
13 #ifndef WIN32_LEAN_AND_MEAN
14 #define WIN32_LEAN_AND_MEAN
15 #endif
16 #include <Windows.h>
17 #endif
18 
22 static WorldStone::IOBase::Path GetInstallDirectory()
23 {
24 #ifdef WS_PLATFORM_WINDOWS
25 #define DIABLO2_KEY "Software\\Blizzard Entertainment\\Diablo II"
26  const HKEY allowedKeys[] = {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
27  BYTE buffer[1024] = {0};
28  for (HKEY baseKey : allowedKeys)
29  {
30  HKEY openedKey = 0;
31  LSTATUS statusCode = RegOpenKeyExA(baseKey, DIABLO2_KEY, 0, KEY_READ, &openedKey);
32  if (statusCode == ERROR_SUCCESS) {
33  DWORD valueType = 0;
34  DWORD valueSize = sizeof(buffer);
35  statusCode =
36  RegQueryValueExA(openedKey, "InstallPath", 0, &valueType, buffer, &valueSize);
37  RegCloseKey(openedKey);
38  if (statusCode == ERROR_SUCCESS && valueType == REG_SZ) {
39  return (const char*)buffer;
40  }
41  }
42  }
43 
44 #endif
45  return {};
46 }
47 
48 char const* const FileBrowser::mpqFiles[] = {"d2char.mpq", "d2data.mpq", "d2exp.mpq",
49  "d2music.mpq", "d2sfx.mpq", "d2speech.mpq",
50  "d2video.mpq", "d2xtalk.mpq", "d2xvideo.mpq"};
51 char const* const FileBrowser::listFiles[] = {"listfile.txt"};
52 
53 FileBrowser::IFileView::~IFileView() {}
54 
55 FileBrowser::~FileBrowser()
56 {
57  // Has to be destroyed before the MpqArchive
58  currentView = nullptr;
59 }
60 
61 void FileBrowser::display(SpriteRenderer& spriteRenderer)
62 {
63  displayMenuBar();
64  ImGui::SetNextWindowSizeConstraints(ImVec2{300.0f, 250.f}, ImVec2{FLT_MAX, FLT_MAX});
65  ImGui::Begin("Files", nullptr, 0 & ImGuiWindowFlags_AlwaysAutoResize);
66  ImGui::PushItemWidth(-1.f);
67 
68  if (fileListWidget.display()) {
69  bx::debugPrintf("New file selected %s\n", fileListWidget.getSelectedElement());
70  onFileSelected(fileListWidget.getSelectedElement());
71  }
72  ImGui::PopItemWidth();
73  ImGui::End();
74 
75  if (currentView) currentView->display(spriteRenderer);
76 }
77 
78 void FileBrowser::displayMenuBar()
79 {
80  // See https://github.com/ocornut/imgui/issues/331
81  const char* openPopupRequest = nullptr;
82  if (ImGui::BeginMainMenuBar()) {
83  if (ImGui::BeginMenu("File")) {
84  if (ImGui::MenuItem("Open", "Ctrl+O")) {
85 
86  openPopupRequest = "Open file";
87  }
88  ImGui::EndMenu();
89  }
90 
91  ImGui::EndMainMenuBar();
92  }
93 
94  if (openPopupRequest) {
95  if (mpqDirectory.size() == 0) {
96  auto installDirectory = GetInstallDirectory();
97  mpqDirectory = installDirectory.size() ? installDirectory : "./";
98  }
99  ImGui::OpenPopup(openPopupRequest);
100  }
101 
102  if (ImGui::BeginPopupModal("Open file", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
103  ImGui::Text("Choose the MPQ file to open.");
104  ImGui::Text("Installation path used:%s", mpqDirectory.c_str());
105  static int item = 0;
106  ImGui::Combo("Mpq File", &item, mpqFiles, sizeof(mpqFiles) / sizeof(*mpqFiles));
107 
108  if (ImGui::Button("Open")) {
109 
110  currentView = nullptr;
111  currentArchive = WorldStone::MpqArchive{(mpqDirectory + mpqFiles[item]).c_str(),
112  (mpqDirectory + listFiles[0]).c_str()};
113  if (!currentArchive.good())
114  currentArchive = WorldStone::MpqArchive{(mpqDirectory + mpqFiles[item]).c_str()};
115  if (currentArchive.good()) {
116  auto fileList = currentArchive.findFiles();
117  std::sort(fileList.begin(), fileList.end());
118  fileListWidget.replaceElements(std::move(fileList));
119  ImGui::CloseCurrentPopup();
120  }
121  else
122  {
123  fileListWidget.replaceElements({});
124  currentArchive = WorldStone::MpqArchive{};
125  ImGui::OpenPopup("Error");
126  }
127  }
128  ImGui::SameLine();
129 
130  if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup();
131 
132  if (ImGui::BeginPopupModal("Error")) {
133  ImGui::Text("Failed to open file %s", (mpqDirectory + mpqFiles[item]).c_str());
134  if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
135  ImGui::EndPopup();
136  }
137 
138  ImGui::EndPopup();
139  }
140 }
141 
142 namespace
143 {
144 
145 struct SpriteAnim
146 {
147 
148  SpriteRenderer::SpriteRenderDataHandle spriteDataHdl;
149 
150  ImVec2 pos{200.f, 200.f};
151  float scale = 1.f;
152  float animationTime = 0.f;
153  float framesPerSecond = 20.f;
154  uint32_t nbFrames = 0;
156 
157  // Options
158  bool drawAABB = true;
159  bool drawGizmo = true;
160 
161  void Display(SpriteRenderer& spriteRenderer)
162  {
163  ImGui::InputFloat2("Translation", (float*)&pos);
164  ImGui::InputFloat("Scale", &scale);
165 
166  float curFrameFloat = animationTime * framesPerSecond;
167 
168  ImGui::SliderFloat("FPS", &framesPerSecond, 0.f, 255.f);
169  if (framesPerSecond > 0.f) {
170  ImGui::Text("Current frame: %f ", double(curFrameFloat));
171  }
172  else
173  {
174  int sliderFrame = int(animationTime);
175  ImGui::SliderInt("Current frame", &sliderFrame, 0, int(nbFrames - 1));
176  sliderFrame = std::min(std::max(sliderFrame, 0), int(nbFrames - 1));
177  curFrameFloat = animationTime = float(sliderFrame);
178  }
179 
180  assert(curFrameFloat >= 0);
181  const uint32_t curFrame = uint32_t(curFrameFloat);
182  assert(curFrame < nbFrames);
183 
184  { // Render
185  ImDrawList* drawList = ImGui::GetWindowDrawList();
186  drawList->PushClipRectFullScreen();
187 
188  // Bounding box
189  ImGui::Checkbox("Box", &drawAABB);
190  if (drawAABB) {
191  drawList->AddRect(
192  ImVec2{pos.x + extents.xLower * scale, pos.y + extents.yLower * scale},
193  ImVec2{pos.x + extents.xUpper * scale, pos.y + extents.yUpper * scale},
194  0xFFAAAAAA);
195  }
196  ImGui::SameLine();
197  ImGui::Checkbox("Gizmo", &drawGizmo);
198  if (drawGizmo) {
199  const float gizmoSize = 10.f;
200  drawList->AddLine(pos, ImVec2{pos.x + gizmoSize, pos.y}, 0xFF0000FF, 1.f);
201  drawList->AddLine(pos, ImVec2{pos.x, pos.y + gizmoSize}, 0xFF00FF00, 1.f);
202 
203  drawList->AddCircle(pos, 20.f, 0xFF00FFFF);
204  }
205  drawList->PopClipRect();
206 
207  spriteRenderer.pushDrawRequest(spriteDataHdl, {{pos.x, pos.y}, curFrame, scale});
208  }
209 
210  // Advance timer
211  if (nbFrames && framesPerSecond > 0.f) {
212  animationTime += ImGui::GetIO().DeltaTime;
213  if (uint32_t(animationTime * framesPerSecond) >= nbFrames)
214  animationTime = std::fmod(animationTime, nbFrames / framesPerSecond);
215  assert(uint32_t(animationTime * framesPerSecond) < nbFrames);
216  }
217  }
218 };
219 
220 struct DccView : public FileBrowser::IFileView
221 {
222 
223  explicit DccView(const WorldStone::MpqArchive::Path& _filePath) : IFileView(_filePath) {}
224  WorldStone::DCC dccFile;
225  WorldStone::DCC::Direction currentDir;
226  int currentDirIndex = 0;
227 
228  SpriteAnim spriteAnim;
229 
230  void display(SpriteRenderer& spriteRenderer) override
231  {
232  const auto& header = dccFile.getHeader();
233  if (ImGui::Begin("DCC")) {
234  IFileView::display();
235  // clang-format off
236  ImGui::Text("Header");
237  ImGui::Separator();
238  ImGui::Text("Version:"); ImGui::SameLine(); ImGui::Text("%hhu", header.version);
239  ImGui::Text("Directions:"); ImGui::SameLine(); ImGui::Text("%hhu", header.directions);
240  ImGui::Text("Frames per dir:"); ImGui::SameLine(); ImGui::Text("%hhu", header.framesPerDir);
241  ImGui::Text("Tag:"); ImGui::SameLine(); ImGui::Text("%u", header.tag);
242  ImGui::Separator();
243  // clang-format on
244  }
245 
246  bool dirChanged =
247  ImGui::SliderInt("Direction", &currentDirIndex, 0, int(header.directions - 1));
248  currentDirIndex = std::min(std::max(currentDirIndex, 0), int(header.directions - 1));
249  if (spriteAnim.spriteDataHdl.expired() || dirChanged) {
250  loadDirectionIntoAnim(uint32_t(currentDirIndex), spriteRenderer);
251  }
252  if (!spriteAnim.spriteDataHdl.expired()) {
253  spriteAnim.Display(spriteRenderer);
254  }
255  else
256  {
257  ImGui::Separator();
258  const ImVec4 red{1.f, 0.f, 0.f, 1.f};
259  ImGui::TextColored(red, "Unable to decode direction %d", currentDirIndex);
260  }
261  ImGui::End();
262  }
263  void loadDirectionIntoAnim(uint32_t direction, SpriteRenderer& spriteRenderer)
264  {
265  spriteAnim = {}; // Reset the sprite animation
266 
267  spriteAnim.spriteDataHdl = spriteRenderer.createSpriteRenderData();
268  const auto spriteRenderDataPtr = spriteAnim.spriteDataHdl.lock();
269 
270  const auto& header = dccFile.getHeader();
271  currentDir = {};
273  if (dccFile.readDirection(currentDir, direction, imageprovider)) {
274  for (size_t i = 0; i < currentDir.frameHeaders.size(); i++)
275  {
276  const auto& frameHeader = currentDir.frameHeaders[i];
277  spriteRenderDataPtr->addSpriteFrame(
278  {int16_t(frameHeader.extents.xLower), int16_t(frameHeader.extents.yLower),
279  uint16_t(frameHeader.extents.width()), uint16_t(frameHeader.extents.height()),
280  imageprovider.getImage(i).buffer});
281  }
282  spriteAnim.extents = currentDir.extents;
283  spriteAnim.nbFrames = header.framesPerDir;
284  }
285  }
286 };
287 
288 struct Dc6View : public FileBrowser::IFileView
289 {
290  explicit Dc6View(const WorldStone::MpqArchive::Path& _filePath) : IFileView(_filePath) {}
291  WorldStone::DC6 dc6File;
292  SpriteAnim spriteAnim;
293  int currentDirIndex = 0;
294 
295  void display(SpriteRenderer& spriteRenderer) override
296  {
297  const auto& header = dc6File.getHeader();
298  if (ImGui::Begin("DC6")) {
299  IFileView::display();
300  // clang-format off
301  ImGui::Text("Header");
302  ImGui::Separator();
303  ImGui::Text("Version:"); ImGui::SameLine(); ImGui::Text("%d", header.version);
304  ImGui::Text("Flags:"); ImGui::SameLine(); ImGui::Text("%d", header.flags);
305  ImGui::Text("Directions:"); ImGui::SameLine(); ImGui::Text("%u", header.directions);
306  ImGui::Text("Frames per dir:"); ImGui::SameLine(); ImGui::Text("%u", header.framesPerDir);
307  ImGui::Separator();
308  // clang-format on
309  }
310 
311  bool dirChanged =
312  ImGui::SliderInt("Direction", &currentDirIndex, 0, int(header.directions - 1));
313  currentDirIndex = std::min(std::max(currentDirIndex, 0), int(header.directions - 1));
314  if (spriteAnim.spriteDataHdl.expired() || dirChanged) {
315  loadDirectionIntoAnim(uint32_t(currentDirIndex), spriteRenderer);
316  }
317  if (!spriteAnim.spriteDataHdl.expired()) {
318  spriteAnim.Display(spriteRenderer);
319  }
320  else
321  {
322  ImGui::Separator();
323  const ImVec4 red{1.f, 0.f, 0.f, 1.f};
324  ImGui::TextColored(red, "Unable to decode direction %d", currentDirIndex);
325  }
326 
327  static bool warningIgnored = false;
328  if (!warningIgnored && ImGui::BeginPopupModal("Warning")) {
329  ImGui::Text("This file contains a lot of frames.\nThis is poorly handled right now and "
330  "might crash if you reload such files too quickly.");
331  if (ImGui::Button("Ignore")) {
332  warningIgnored = true;
333  ImGui::CloseCurrentPopup();
334  }
335  ImGui::SameLine();
336  if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
337  ImGui::EndPopup();
338  }
339 
340  ImGui::End();
341  }
342 
343  void loadDirectionIntoAnim(uint32_t direction, SpriteRenderer& spriteRenderer)
344  {
345  spriteAnim = {}; // Reset the sprite animation
346 
347  spriteAnim.spriteDataHdl = spriteRenderer.createSpriteRenderData();
348  const auto spriteRenderDataPtr = spriteAnim.spriteDataHdl.lock();
349 
350  const auto& header = dc6File.getHeader();
351  const auto& frameHeaders = dc6File.getFrameHeaders();
352  const size_t directionFrameOffset = size_t(direction) * header.framesPerDir;
353 
354  spriteAnim.extents.initializeForExtension();
355  if (header.framesPerDir > 500) ImGui::OpenPopup("Warning");
356 
357  for (size_t frameIndex = 0; frameIndex < header.framesPerDir; frameIndex++)
358  {
359  const size_t frameIndexInFile = directionFrameOffset + frameIndex;
360  const auto& frameHeader = frameHeaders[frameIndexInFile];
361  const WorldStone::AABB<int32_t> frameExtents{
362  frameHeader.offsetX, frameHeader.offsetY - frameHeader.height,
363  frameHeader.offsetX + frameHeader.width,
364  frameHeader.offsetY // The offset is the position of the bottom of the frame
365  };
366  assert(frameExtents.width() == frameHeader.width);
367  assert(frameExtents.height() == frameHeader.height);
368 
369  spriteAnim.extents.extend(frameExtents);
370  auto frameData = dc6File.decompressFrame(frameIndexInFile);
371  spriteRenderDataPtr->addSpriteFrame(
372  {int16_t(frameExtents.xLower), int16_t(frameExtents.yLower),
373  uint16_t(frameExtents.width()), uint16_t(frameExtents.height()),
374  frameData.data()});
375 
376  spriteAnim.nbFrames = header.framesPerDir;
377  }
378  }
379 };
380 
381 struct CofView : public FileBrowser::IFileView
382 {
383  explicit CofView(const WorldStone::MpqArchive::Path& _filePath) : IFileView(_filePath) {}
384  using COF = WorldStone::COF;
385  COF cofFile;
386  int currentLayerIdx = 1;
387 
388  void display() override
389  {
390  const auto& header = cofFile.getHeader();
391  if (ImGui::Begin("COF")) {
392  IFileView::display();
393  // clang-format off
394  ImGui::Text("Header");
395  ImGui::Separator();
396  ImGui::Text("Layers:"); ImGui::SameLine(); ImGui::Text("%hhu", header.layers);
397  ImGui::Text("Frames:"); ImGui::SameLine(); ImGui::Text("%hhu", header.frames);
398  ImGui::Text("Directions:"); ImGui::SameLine(); ImGui::Text("%hhu", header.directions);
399  ImGui::Text("Version:"); ImGui::SameLine(); ImGui::Text("%hhu", header.version);
400  ImGui::NewLine();
401  ImGui::Text("unknown dword(bytes 7-4):");
402  {
403  const uint8_t byte0 = (header.unknown1 >> (8 * 0)) & 0xff;
404  const uint8_t byte1 = (header.unknown1 >> (8 * 1)) & 0xff;
405  const uint8_t byte2 = (header.unknown1 >> (8 * 2)) & 0xff;
406  const uint8_t byte3 = (header.unknown1 >> (8 * 3)) & 0xff;
407  ImGui::Text("0x%08x", header.unknown1);
408  ImGui::SameLine(); ImGui::ColorButton("##color", ImVec4{ byte3 / 255.f, byte2 / 255.f, byte1 / 255.f, byte0 / 255.f }, ImGuiColorEditFlags_NoPicker);
409  const std::string unkAsBin = fmt::format("{:08b} {:08b} {:08b} {:08b}", byte3, byte2, byte1, byte0);
410  ImGui::Text(unkAsBin.data());
411  const std::string unkAsInt = fmt::format("{:03} {:03} {:03} {:03}", byte3, byte2, byte1, byte0);
412  ImGui::Text(unkAsInt.data());
413  }
414  ImGui::NewLine();
415  ImGui::Text("AABB:"); ImGui::SameLine(); ImGui::Text("(%d,%d) -> (%d,%d)", header.xMin, header.yMin, header.xMax, header.yMax);
416  ImGui::Text("AnimRate:"); ImGui::SameLine(); ImGui::Text("%hd", header.animRate);
417  ImGui::Text("zeros:"); ImGui::SameLine(); ImGui::Text("%hx", header.zeros);
418 
419  // clang-format on
420  ImGui::Separator();
421  ImGui::SliderInt("Layer", &currentLayerIdx, 1, header.layers);
422  const COF::Layer& layer = cofFile.getLayers()[size_t(currentLayerIdx - 1)];
423  ImGui::Text("Component:%s", layer.component < COF::componentsNumber
424  ? COF::componentsNames[layer.component]
425  : "Invalid");
426  ImGui::Text("Casts shadow: %d", layer.castsShadow);
427  ImGui::Text("Is selectable: %d", layer.isSelectable);
428  ImGui::Text("Override transparency level: %d", layer.overrideTranslvl);
429  ImGui::Text("New transparency level: %d", layer.newTranslvl);
430  ImGui::Text("Weapon class: %s", layer.weaponClass);
431  }
432  ImGui::End();
433  }
434 };
435 
436 struct PaletteView final : public FileBrowser::IFileView
437 {
438  using Palette = WorldStone::Palette;
439  std::unique_ptr<Palette> palettePtr;
440  bgfx::TextureHandle paletteTexture = BGFX_INVALID_HANDLE;
441  bool alreadySetAsCurrentPalette = false;
442 
443  PaletteView(const WorldStone::MpqArchive::Path& _filePath, std::unique_ptr<Palette> inPalette)
444  : IFileView(_filePath), palettePtr(std::move(inPalette))
445  {
446  const Palette& palette = *palettePtr;
447  static_assert(sizeof(WorldStone::Palette::Color24Bits) == 3, "");
448  auto paletteRGB888 =
449  bgfx::alloc(sizeof(WorldStone::Palette::Color24Bits) * WorldStone::Palette::colorCount);
450  for (size_t i = 0; i < WorldStone::Palette::colorCount; i++)
451  {
452  const WorldStone::Palette::Color color = palette.colors[i];
453  ((WorldStone::Palette::Color24Bits*)paletteRGB888->data)[i] = {color.r, color.g,
454  color.b};
455  }
456  paletteTexture =
457  bgfx::createTexture2D(256, 1, false, 1, bgfx::TextureFormat::RGB8,
458  BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT, paletteRGB888);
459  }
460  ~PaletteView() { bgfx::destroy(paletteTexture); }
461  void display(SpriteRenderer& spriteRenderer) override
462  {
463  ImGui::SetNextWindowSize(ImGui::GetContentRegionAvail(), ImGuiCond_FirstUseEver);
464  if (ImGui::Begin("Palette")) {
465  IFileView::display();
466  if (!alreadySetAsCurrentPalette && ImGui::Button("Set as current palette")) {
467  spriteRenderer.setPalette(*palettePtr);
468  alreadySetAsCurrentPalette = true;
469  }
470  // Can add some more info here
471  ImVec2 curWindowSize = ImGui::GetContentRegionAvail();
472  ImGui::Image(paletteTexture, ImVec2{curWindowSize.x, curWindowSize.y});
473  }
474  ImGui::End();
475  }
476 };
477 
478 class PL2View final : public FileBrowser::IFileView
479 {
480 private:
481  using Palette = WorldStone::Palette;
482  using PalShiftTransform = WorldStone::PalShiftTransform;
483  using PL2 = WorldStone::PL2;
484  std::unique_ptr<PL2> pl2;
485  bgfx::TextureHandle paletteTexture = BGFX_INVALID_HANDLE;
486  size_t textureWidth, textureHeight;
487 
488  int currentMin = 0;
489  int currentMax = 0;
490 
491 public:
492  PL2View(const WorldStone::MpqArchive::Path& _filePath, std::unique_ptr<PL2> _pl2)
493  : IFileView(_filePath), pl2(std::move(_pl2))
494  {
495 
496  using WorldStone::Utils::Size;
497  static_assert(sizeof(Palette::Color24Bits) == 3, "");
498 
499  // clangformat off
500  textureWidth = WorldStone::Palette::colorCount;
501  textureHeight = 1 // pl2->basePalette
502  + Size(pl2->lightLevelVariations) + Size(pl2->invColorVariations)
503  + 1 // pl2->selectedUnitShift
504  + Size(pl2->alphaBlend) * Size(pl2->alphaBlend[0])
505  + Size(pl2->additiveBlend) + Size(pl2->multiplicativeBlend)
506  + Size(pl2->hueVariations) + 1 // pl2->redTones
507  + 1 // pl2->greenTones
508  + 1 // pl2->blueTones
509  + Size(pl2->unknownColorVariations) + Size(pl2->maxComponentBlend)
510  + 1 // pl2->darkenedColorShift
511  + Size(pl2->textColorShifts);
512  currentMax = int(textureHeight - 1);
513  // clangformat on
514 
515  const size_t textureSizeInBytes =
516  sizeof(Palette::Color24Bits) * textureWidth * textureHeight;
517  auto paletteRGB888 = bgfx::alloc(uint32_t(textureSizeInBytes));
518  const Palette& palette = pl2->basePalette;
519  // dstColors pointer will be incremented accordingly
520  const auto palShiftToRGB = [&palette](const PalShiftTransform& transform,
521  Palette::Color24Bits*& dstColors) {
522  for (uint8_t shiftIndex : transform.indices)
523  {
524  const Palette::Color color = palette.colors[shiftIndex];
525  *(dstColors++) = Palette::Color24Bits{color.r, color.g, color.b};
526  }
527  };
528  Palette::Color24Bits* const textureDataBegin = (Palette::Color24Bits*)paletteRGB888->data;
529  Palette::Color24Bits* textureData = textureDataBegin;
530  // Copy the base palette
531  for (const Palette::Color color : palette.colors)
532  {
533  *(textureData++) = {color.r, color.g, color.b};
534  }
535 
536  // Copy the light variations
537  for (const PalShiftTransform& transform : pl2->lightLevelVariations)
538  {
539  palShiftToRGB(transform, textureData);
540  }
541  for (const PalShiftTransform& transform : pl2->invColorVariations)
542  {
543  palShiftToRGB(transform, textureData);
544  }
545  palShiftToRGB(pl2->selectedUnitShift, textureData);
546  for (const auto& alphaBlends : pl2->alphaBlend)
547  {
548  for (const PalShiftTransform& transform : alphaBlends)
549  {
550  palShiftToRGB(transform, textureData);
551  }
552  }
553  for (const PalShiftTransform& transform : pl2->additiveBlend)
554  {
555  palShiftToRGB(transform, textureData);
556  }
557  for (const PalShiftTransform& transform : pl2->multiplicativeBlend)
558  {
559  palShiftToRGB(transform, textureData);
560  }
561  for (const PalShiftTransform& transform : pl2->hueVariations)
562  {
563  palShiftToRGB(transform, textureData);
564  }
565  palShiftToRGB(pl2->redTones, textureData);
566  palShiftToRGB(pl2->greenTones, textureData);
567  palShiftToRGB(pl2->blueTones, textureData);
568  for (const PalShiftTransform& transform : pl2->unknownColorVariations)
569  {
570  palShiftToRGB(transform, textureData);
571  }
572  for (const PalShiftTransform& transform : pl2->maxComponentBlend)
573  {
574  palShiftToRGB(transform, textureData);
575  }
576  palShiftToRGB(pl2->darkenedColorShift, textureData);
577  for (const PalShiftTransform& transform : pl2->textColorShifts)
578  {
579  palShiftToRGB(transform, textureData);
580  }
581 
582  assert(textureData == textureDataBegin + textureWidth * textureHeight);
583  paletteTexture = bgfx::createTexture2D(
584  uint16_t(textureWidth), uint16_t(textureHeight), false, 1, bgfx::TextureFormat::RGB8,
585  BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT, paletteRGB888);
586  }
587 
588  ~PL2View() { bgfx::destroy(paletteTexture); }
589  void display() override
590  {
591  ImGui::SetNextWindowSize(ImGui::GetContentRegionAvail(), ImGuiCond_FirstUseEver);
592  if (ImGui::Begin("PL2")) {
593  IFileView::display();
594  const int maxVal = int(textureHeight - 1);
595  ImGui::DragIntRange2("Display range", &currentMin, &currentMax, 1.f, 0, maxVal);
596  // Need to clamp, see issue https://github.com/ocornut/imgui/issues/1441
597  currentMin = std::min(std::max(currentMin, 0), maxVal);
598  currentMax = std::min(std::max(currentMax, currentMin), maxVal);
599 
600  ImVec2 curWindowSize = ImGui::GetContentRegionAvail();
601  const float pixelSize = 1.f / textureHeight;
602 
603  const float startY = currentMin * pixelSize;
604  const float endY = (currentMax + 1) * pixelSize;
605  ImGui::Image(paletteTexture, ImVec2{curWindowSize.x, curWindowSize.y},
606  ImVec2{0.f, startY}, ImVec2{1.f, endY});
607  }
608  ImGui::End();
609  }
610 };
611 
612 } // anonymous namespace
613 
614 static void toLowerCase(std::string& str)
615 {
616  for (char& c : str)
617  {
618  c = char(tolower(c));
619  }
620 }
621 
622 void FileBrowser::onFileSelected(const char* fileName)
623 {
624  std::string fileNameStr{fileName};
625  size_t lastDot = fileNameStr.find_last_of('.');
626  if (lastDot != std::string::npos) {
627  std::string extension = fileNameStr.substr(lastDot + 1);
628  toLowerCase(extension);
629  if (extension == "dcc") {
630  auto dccView = std::make_unique<DccView>(fileNameStr);
631  if (dccView->dccFile.initDecoder(currentArchive.open(fileNameStr))) {
632  currentView = std::move(dccView);
633  }
634  }
635  else if (extension == "dc6")
636  {
637  auto dc6View = std::make_unique<Dc6View>(fileNameStr);
638  if (dc6View->dc6File.initDecoder(currentArchive.open(fileNameStr))) {
639  currentView = std::move(dc6View);
640  }
641  }
642  else if (extension == "cof")
643  {
644  auto cofView = std::make_unique<CofView>(fileNameStr);
645  if (cofView->cofFile.read(currentArchive.open(fileNameStr))) {
646  currentView = std::move(cofView);
647  }
648  }
649  else if (extension == "dat")
650  {
651  auto palette = std::make_unique<WorldStone::Palette>();
652  auto paletteFile = currentArchive.open(fileNameStr);
653  if (palette->decode(paletteFile.get())) {
654  currentView = std::make_unique<PaletteView>(fileNameStr, std::move(palette));
655  }
656  }
657  else if (extension == "pl2")
658  {
659  auto paletteFile = currentArchive.open(fileNameStr);
660  auto pl2 = WorldStone::PL2::ReadFromStream(paletteFile.get());
661  if (pl2) {
662  currentView = std::make_unique<PL2View>(fileNameStr, std::move(pl2));
663  }
664  }
665  else
666  {
667  currentView = nullptr;
668  }
669  }
670 }
Decoder for the DCC image format.
Definition: dcc.h:46
A wrapper to manage MPQ archives.
Definition: MpqArchive.h:17
A simple image provider that allocates a new buffer for each call to getNewImage() ...
Definition: ImageView.h:140
ImageView< Color > getImage(size_t imageIndex)
Definition: ImageView.h:168
Precomputed palette variations in the form of palette shifts.
Definition: Palette.h:67
const char * getSelectedElement()
Decoder for the DC6 image format.
Definition: dc6.h:39
void replaceElements(Vector< std::string > newElements)
Replace all the elements of the list by a new one.
bool display()
Display the filter and the ListBox.
Implementation of a DCC file decoder.
Decoder for the COF (Components Object File) file format.
Definition: cof.h:24
Helper to load a Diablo 2 palette (.pal/.dat format)
Definition: Palette.h:21