aboutsummaryrefslogtreecommitdiff
path: root/libs/preview_dds_native/src/ddsfile.cpp
blob: 91dc627c59c0e9bc2a9bd953b707263373ddbd1c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#include "ddsfile.h"

#include <QFile>

#include <algorithm>
#include <cstring>

bool DDSFile::loadFromFile(const QString& path)
{
  QFile file(path);
  if (!file.open(QIODevice::ReadOnly)) {
    return false;
  }
  return parse(file.readAll());
}

bool DDSFile::loadFromData(const QByteArray& data)
{
  return parse(data);
}

bool DDSFile::parse(const QByteArray& data)
{
  const char* ptr = data.constData();
  int offset      = 0;
  int size        = data.size();

  // Magic number
  if (size < 4)
    return false;
  uint32_t magic;
  std::memcpy(&magic, ptr + offset, 4);
  offset += 4;
  if (magic != DDS_MAGIC)
    return false;

  // DDS header
  if (size - offset < static_cast<int>(sizeof(DDSHeader)))
    return false;
  DDSHeader header;
  std::memcpy(&header, ptr + offset, sizeof(DDSHeader));
  offset += sizeof(DDSHeader);

  if (header.dwSize != 124)
    return false;

  // DXT10 extended header
  DDSHeaderDXT10 dxt10{};
  bool hasDXT10 = false;
  if ((header.ddspf.dwFlags & DDPF_FOURCC) &&
      header.ddspf.dwFourCC == makeFourCC('D', 'X', '1', '0')) {
    if (size - offset < static_cast<int>(sizeof(DDSHeaderDXT10)))
      return false;
    std::memcpy(&dxt10, ptr + offset, sizeof(DDSHeaderDXT10));
    offset += sizeof(DDSHeaderDXT10);
    hasDXT10 = true;
  }

  m_width  = header.dwWidth;
  m_height = header.dwHeight;

  // Determine GL format
  m_glFormat = getGLFormat(header.ddspf, hasDXT10 ? &dxt10 : nullptr);
  if (!m_glFormat.valid) {
    return false;
  }

  // Determine DXGI format for size calculations
  if (hasDXT10) {
    m_dxgiFormat = dxt10.dxgiFormat;
  } else if (header.ddspf.dwFlags & DDPF_FOURCC) {
    m_dxgiFormat = fourCCToDXGI(header.ddspf.dwFourCC);
  } else {
    m_dxgiFormat = DXGIFormat::UNKNOWN;
  }

  // Description
  m_description = formatDescription(header.ddspf, hasDXT10 ? &dxt10 : nullptr);
  m_description +=
      QString(" | %1x%2").arg(m_width).arg(m_height);

  // Cubemap detection
  m_cubemap  = false;
  int layers = 1;
  if (header.dwCaps2 & DDSCAPS2_CUBEMAP) {
    m_cubemap = true;
    layers    = 0;
    if (header.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEX) layers++;
    if (header.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEX) layers++;
    if (header.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEY) layers++;
    if (header.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEY) layers++;
    if (header.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEZ) layers++;
    if (header.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ) layers++;
    m_description += " | Cubemap";
  } else {
    m_description += " | 2D";
  }

  // Mipmap count
  int mipCount = 1;
  if (header.dwFlags & DDSD_MIPMAPCOUNT) {
    mipCount = std::max(1u, header.dwMipMapCount);
  }
  if (mipCount > 1) {
    m_description += QString(" | %1 mips").arg(mipCount);
  }

  // Read pixel data for each face and mip level
  m_faces.resize(layers);
  for (int face = 0; face < layers; ++face) {
    m_faces[face].mips.resize(mipCount);
    uint32_t w = m_width;
    uint32_t h = m_height;
    for (int mip = 0; mip < mipCount; ++mip) {
      uint32_t dataSize = mipDataSize(m_dxgiFormat, header.ddspf, w, h);
      if (offset + static_cast<int>(dataSize) > size) {
        // Truncated file, keep what we have
        m_faces[face].mips.resize(mip);
        break;
      }
      m_faces[face].mips[mip].width  = w;
      m_faces[face].mips[mip].height = h;
      m_faces[face].mips[mip].data =
          QByteArray(ptr + offset, static_cast<int>(dataSize));
      offset += dataSize;
      w = std::max(1u, w / 2);
      h = std::max(1u, h / 2);
    }
  }

  return !m_faces.isEmpty() && !m_faces[0].mips.isEmpty();
}