jjzjj

c++ - 将 S3TC/DXTn 数据转换为 QImage

coder 2024-02-22 原文

我加入了一个简化遗留图形代码的项目,非常感谢有关此数据转换问题的建议。

输入是 DXT1、DXT3、DXT5 格式的压缩纹理。数据在主内存中,而不是显卡内存中。输入没有标准 DDS_HEADER ,只有压缩像素数据。所需的输出是 QImages。

使用现有的元数据,我们可以构造一个 DDS_HEADER,将纹理写入临时文件,然后从该文件加载 QImage。然而,我们希望避免这种解决方案并直接使用原始数据,因为它有很多很多实例。

我的研究没有发现任何 Qt 函数可以直接执行此转换。到目前为止,听起来最有前途的方法是使用我们现有的 OpenGL 上下文将纹理绘制到 QOpenGLFrameBufferObject。这个类有一个 toImage() 成员函数。但是,我不明白如何从原始数据构造可用的纹理对象并将其绘制到帧缓冲区。

编辑:基于 Scheff 的出色回答的澄清。我知道可以手动解压缩纹理并从结果中加载 QImage。为了最简单起见,我宁愿避免此步骤并尽可能使用库函数。 QOpenGLTexture有一个成员函数setCompressedData可以用到。

感谢您的任何建议。

最佳答案

看到这个问题,我开始好奇,了解了S3 Texture Compression .有趣的是,虽然我过去很关心压缩纹理,但我一直认为它会像 LZW Algorithm 这样复杂的东西。或 JPEG Compression ,并且从未深入挖掘。但是,今天我意识到我完全错了。

S3 Texture Compression尽管它可以实现相当可观的压缩比,但实际上要简单得多。

好的介绍google一下就能轻松找到。这个问题已经提到了 MSDN。此外,我还找到了一些其他网站,这些网站在最短的时间内为我很好地介绍了这个主题:

关于GitHub项目,好像已经有人做了。我用眼睛扫描了一点代码,但最后,我不确定它们是否支持所有可能的功能。然而,我从 Brandon Jones 网站“借用”了测试图像,所以,提一下就足够了。

所以,这是我的实际答案:另一种方法可能是在 CPU 端将纹理完全解码到 QImage。

作为概念证明,我留下了今天早上我修改代码的结果——我尝试将链接描述转换为工作 C++ 代码——DXT1-QImage.cc:

#include <cstdint>
#include <fstream>

#include <QtWidgets>

#ifndef _WIN32
typedef quint32 DWORD;
#endif // _WIN32

/* borrowed from:
 * https://msdn.microsoft.com/en-us/library/windows/desktop/bb943984(v=vs.85).aspx
 */
struct DDS_PIXELFORMAT {
  DWORD dwSize;
  DWORD dwFlags;
  DWORD dwFourCC;
  DWORD dwRGBBitCount;
  DWORD dwRBitMask;
  DWORD dwGBitMask;
  DWORD dwBBitMask;
  DWORD dwABitMask;
};
/* borrowed from:
 * https://msdn.microsoft.com/en-us/library/windows/desktop/bb943982(v=vs.85).aspx
 */
struct DDS_HEADER {
  DWORD           dwSize;
  DWORD           dwFlags;
  DWORD           dwHeight;
  DWORD           dwWidth;
  DWORD           dwPitchOrLinearSize;
  DWORD           dwDepth;
  DWORD           dwMipMapCount;
  DWORD           dwReserved1[11];
  DDS_PIXELFORMAT ddspf;
  DWORD           dwCaps;
  DWORD           dwCaps2;
  DWORD           dwCaps3;
  DWORD           dwCaps4;
  DWORD           dwReserved2;
};

inline quint32 stretch(std::uint16_t color)
{
  return 0xff000000u
    | (quint32)(color & 0x001f) << 3 // >>  0 << 3 <<  0
    | (quint32)(color & 0x07e0) << 5 // >>  5 << 2 <<  8
    | (quint32)(color & 0xf800) << 8;// >> 11 << 3 << 16
}

void makeLUT(
  quint32 lut[4], std::uint16_t color0, std::uint16_t color1)
{
  const quint32 argb0 = stretch(color0);
  const quint32 argb1 = stretch(color1);
  lut[0] = argb0;
  lut[1] = argb1;
  if (color0 > color1) {
    lut[2] = 0xff000000u
      | ((((argb0 & 0xff0000) >> 15) + ((argb1 & 0xff0000) >> 16)) / 3) << 16
      | ((((argb0 & 0x00ff00) >>  7) + ((argb1 & 0x00ff00) >>  8)) / 3) <<  8
      | ((((argb0 & 0x0000ff) <<  1) + ((argb1 & 0x0000ff) >>  0)) / 3) <<  0;
    lut[3] = 0xff000000u
      | ((((argb1 & 0xff0000) >> 15) + ((argb0 & 0xff0000) >> 16)) / 3) << 16
      | ((((argb1 & 0x00ff00) >>  7) + ((argb0 & 0x00ff00) >>  8)) / 3) <<  8
      | ((((argb1 & 0x0000ff) <<  1) + ((argb0 & 0x0000ff) >>  0)) / 3) <<  0;
  } else {
    lut[2] = 0xff000000u
      | ((((argb0 & 0xff0000) >> 16) + ((argb1 & 0xff0000) >> 16)) / 2) << 16
      | ((((argb0 & 0x00ff00) >>  8) + ((argb1 & 0x00ff00) >>  8)) / 2) <<  8
      | ((((argb0 & 0x0000ff) >>  0) + ((argb1 & 0x0000ff) >>  0)) / 2) <<  0;
    lut[3] = 0xff000000u;
  }
}

const std::uint8_t* uncompress(
  const std::uint8_t *data, QImage &qImg, int x, int y)
{
  // get color 0 and color 1
  std::uint16_t color0 = data[0] | data[1] << 8;
  std::uint16_t color1 = data[2] | data[3] << 8;
  data += 4;
  quint32 lut[4]; makeLUT(lut, color0, color1);
  // decode 4 x 4 pixels
  for (int i = 0; i < 4; ++i) {
    qImg.setPixel(x + 0, y + i, lut[data[i] >> 0 & 3]);
    qImg.setPixel(x + 1, y + i, lut[data[i] >> 2 & 3]);
    qImg.setPixel(x + 2, y + i, lut[data[i] >> 4 & 3]);
    qImg.setPixel(x + 3, y + i, lut[data[i] >> 6 & 3]);
  }
  data += 4;
  // done
  return data;
}

QImage loadDXT1(const char *file)
{
  std::ifstream fIn(file, std::ios::in | std::ios::binary);
  // read magic code
  enum { sizeMagic = 4 }; char magic[sizeMagic];
  if (!fIn.read(magic, sizeMagic)) {
    return QImage(); // ERROR: read failed
  }
  if (strncmp(magic, "DDS ", sizeMagic) != 0) {
    return QImage(); // ERROR: wrong format (wrong magic code)
  }
  // read header
  DDS_HEADER header;
  if (!fIn.read((char*)&header, sizeof header)) {
    return QImage(); // ERROR: read failed
  }
  qDebug() << "header size:" << sizeof header;
  // get raw data (size computation unclear)
  const unsigned w = (header.dwWidth + 3) / 4;
  const unsigned h = (header.dwHeight + 3) / 4;
  std::vector<std::uint8_t> data(w * h * 8);
  qDebug() << "data size:" << data.size();
  if (!fIn.read((char*)data.data(), data.size())) {
    return QImage(); // ERROR: read failed
  }
  // decode raw data
  QImage qImg(header.dwWidth, header.dwHeight, QImage::Format_ARGB32);
  const std::uint8_t *pData = data.data();
  for (int y = 0; y < (int)header.dwHeight; y += 4) {
    for (int x = 0; x < (int)header.dwWidth; x += 4) {
      pData = uncompress(pData, qImg, x, y);
    }
  }
  qDebug() << "processed image size:" << fIn.tellg();
  // done 
  return qImg;
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // build QImage
  QImage qImg = loadDXT1("test-dxt1.dds");
  // setup GUI
  QMainWindow qWin;
  QLabel qLblImg;
  qLblImg.setPixmap(QPixmap::fromImage(qImg));
  qWin.setCentralWidget(&qLblImg);
  qWin.show();
  // exec. application
  return app.exec();
}

我是在VS2013上开发调试的。为了检查它是否可以移植到 Linux,我能做的最好的事情就是在 cygwin 上编译和测试。

为此,我写了一个QMake文件DXT1-QImage.pro:

SOURCES = DXT1-QImage.cc

QT += widgets

bash 中编译和运行:

$ qmake-qt5 DXT1-QImage.pro 

$ make
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o DXT1-QImage.o DXT1-QImage.cc
g++  -o DXT1-QImage.exe DXT1-QImage.o   -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread 

$ ./DXT1-QImage 
Qt Version: 5.9.2
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-ds32737'
header size: 124
data size: 131072
processed image size: 131200
QXcbShmImage: shmget() failed (88: Function not implemented) for size 1048576 (512x512)

为了测试,我使用了示例文件 test-dxt1.dds .

结果是这样的:

为了对比,原图:

注意事项:

尽管发问者明确提到他想转换已经在内存中的原始图像数据,但我实现了一个文件加载器。我不得不这样做,因为我没有看到任何其他方法可以将(有效的)DXT1 原始数据存入我这边的内存中(以便事后证明它是否有效)。

我的调试输出显示我的加载器读取了 131200 字节(即 4 字节魔术代码、124 字节 header 和 131072 字节压缩图像数据)。 与此相反,文件 test-dxt1.dds包含 174904 个字节。所以,文件中有额外的数据,但我(还)不知道它有什么用。

关于c++ - 将 S3TC/DXTn 数据转换为 QImage,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48135093/

有关c++ - 将 S3TC/DXTn 数据转换为 QImage的更多相关文章

  1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  2. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  5. ruby - 将散列转换为嵌套散列 - 2

    这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

  6. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  7. ruby-on-rails - rails : save file from URL and save it to Amazon S3 - 2

    从给定URL下载文件并立即将其上传到AmazonS3的更直接的方法是什么(+将有关文件的一些信息保存到数据库中,例如名称、大小等)?现在,我既不使用Paperclip,也不使用Carrierwave。谢谢 最佳答案 简单明了:require'open-uri'require's3'amazon=S3::Service.new(access_key_id:'KEY',secret_access_key:'KEY')bucket=amazon.buckets.find('image_storage')url='http://www.ex

  8. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  9. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  10. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

随机推荐