ff4ff35918
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
430 lines
12 KiB
C++
430 lines
12 KiB
C++
// Copyright (C) 2023 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
|
|
|
#include "volumetexturedata.h"
|
|
#include "qthread.h"
|
|
#include <QSize>
|
|
#include <QFile>
|
|
#include <QElapsedTimer>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
enum ExampleId { Helix, Box, Colormap };
|
|
|
|
// Method to convert data from T to uint8_t
|
|
template<typename T>
|
|
static void convertData(QByteArray &imageData, const QByteArray &imageDataSource)
|
|
{
|
|
Q_ASSERT(imageDataSource.size() > 0);
|
|
constexpr auto kScale = sizeof(T) / sizeof(uint8_t);
|
|
auto imageDataSourceData = reinterpret_cast<const T *>(imageDataSource.constData());
|
|
qsizetype imageDataSourceSize = imageDataSource.size() / kScale;
|
|
imageData.resize(imageDataSourceSize);
|
|
auto imageDataPtr = reinterpret_cast<uint8_t *>(imageData.data());
|
|
|
|
T min = std::numeric_limits<T>::max();
|
|
T max = std::numeric_limits<T>::min();
|
|
|
|
#pragma omp parallel for
|
|
for (int i = 0; i < imageDataSourceSize; i++) {
|
|
if (imageDataSourceData[i] > max) {
|
|
#pragma omp critical
|
|
max = qMax(max, imageDataSourceData[i]);
|
|
}
|
|
}
|
|
|
|
#pragma omp parallel for
|
|
for (int i = 0; i < imageDataSourceSize; i++) {
|
|
if (imageDataSourceData[i] < min) {
|
|
#pragma omp critical
|
|
min = qMin(min, imageDataSourceData[i]);
|
|
}
|
|
}
|
|
const T range = max - min;
|
|
const double rangeInv = 255.0 / range; // use double for optimal precision
|
|
|
|
#pragma omp parallel for
|
|
for (int i = 0; i < imageDataSourceSize; i++) {
|
|
imageDataPtr[i] = (imageDataSourceData[i] - min) * rangeInv;
|
|
}
|
|
}
|
|
|
|
static QByteArray createBuiltinVolume(int exampleId)
|
|
{
|
|
constexpr int size = 256;
|
|
|
|
QByteArray byteArray(size * size * size, 0);
|
|
uint8_t *data = reinterpret_cast<uint8_t *>(byteArray.data());
|
|
const auto cellIndex = [size](int x, int y, int z) {
|
|
Q_UNUSED(size); // MSVC specific
|
|
const int index = x + size * (z + size * y);
|
|
Q_ASSERT(index < size * size * size && index >= 0);
|
|
return index;
|
|
};
|
|
|
|
const auto createHelix = [&](float zOffset, uint8_t color) {
|
|
// x = radius * cos(t)
|
|
// y = radius * sin(t)
|
|
// z = climb * t
|
|
//
|
|
// We go through t until z is outside of box
|
|
|
|
constexpr float radius = 70.f;
|
|
constexpr float climb = 15.f;
|
|
constexpr float offset = 256 / 2;
|
|
constexpr int thick = 6; // half radius
|
|
|
|
int i = -1;
|
|
QVector3D lastCell = QVector3D(0, 0, 0);
|
|
while (true) {
|
|
i++;
|
|
const float t = i * 0.005f;
|
|
const int cellX = offset + radius * qCos(t);
|
|
const int cellY = offset + radius * qSin(t);
|
|
const int cellZ = (climb * t) - zOffset;
|
|
if (cellZ < 0) {
|
|
continue;
|
|
}
|
|
if (cellZ > 255)
|
|
break;
|
|
|
|
QVector3D originalCell(cellX, cellY, cellZ);
|
|
if (originalCell == lastCell)
|
|
continue;
|
|
lastCell = originalCell;
|
|
|
|
#pragma omp parallel for
|
|
for (int z = cellZ - thick; z < cellZ + thick; z++) {
|
|
if (z < 0 || z > 255)
|
|
continue;
|
|
for (int y = cellY - thick; y < cellY + thick; y++) {
|
|
if (y < 0 || y > 255)
|
|
continue;
|
|
for (int x = cellX - thick; x < cellX + thick; x++) {
|
|
if (x < 0 || x > 255)
|
|
continue;
|
|
QVector3D currCell(x, y, z);
|
|
float dist = originalCell.distanceToPoint(currCell);
|
|
if (dist < thick) {
|
|
data[cellIndex(x, y, z)] = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if (exampleId == ExampleId::Helix) {
|
|
// Fill with weird ball and holes
|
|
QVector3D centreCell(size / 2, size / 2, size / 2);
|
|
#pragma omp parallel for
|
|
for (int z = 0; z < size; z++) {
|
|
for (int y = 0; y < size; y++) {
|
|
for (int x = 0; x < size; x++) {
|
|
const float dist = centreCell.distanceToPoint(QVector3D(x, y, z));
|
|
const float value = dist * 0.5f - 40.f; // Negative value means cell is inside of sphere
|
|
data[cellIndex(x, y, z)] = value >= 0 ? quint8(qBound(value, 0.f, 80.f)) : 80;
|
|
}
|
|
}
|
|
}
|
|
createHelix(0, 200);
|
|
createHelix(30, 150);
|
|
createHelix(60, 100);
|
|
|
|
} else if (exampleId == ExampleId::Colormap) {
|
|
#pragma omp parallel for
|
|
for (int z = 0; z < 256; z++) {
|
|
for (int y = 0; y < 256; y++) {
|
|
for (int x = 0; x < 256; x++) {
|
|
data[cellIndex(x, y, z)] = x;
|
|
}
|
|
}
|
|
}
|
|
} else if (exampleId == ExampleId::Box) {
|
|
std::array<int, 6> colors = { 50, 100, 255, 200, 150, 10 };
|
|
constexpr int width = 10;
|
|
#pragma omp parallel for
|
|
for (int i = 0; i < width; i++) {
|
|
int x0 = i;
|
|
int x1 = 255 - i;
|
|
for (int z = 0; z < 256; z++) {
|
|
for (int y = 0; y < 256; y++) {
|
|
data[cellIndex(x0, y, z)] = colors[0];
|
|
data[cellIndex(x1, y, z)] = colors[1];
|
|
}
|
|
}
|
|
}
|
|
#pragma omp parallel for
|
|
for (int i = 0; i < width; i++) {
|
|
int y0 = i;
|
|
int y1 = 255 - i;
|
|
for (int z = 0; z < 256; z++) {
|
|
for (int x = 0; x < 256; x++) {
|
|
data[cellIndex(x, y0, z)] = colors[2];
|
|
data[cellIndex(x, y1, z)] = colors[3];
|
|
}
|
|
}
|
|
}
|
|
#pragma omp parallel for
|
|
for (int i = 0; i < width; i++) {
|
|
int z0 = i;
|
|
int z1 = 255 - i;
|
|
for (int y = 0; y < 256; y++) {
|
|
for (int x = 0; x < 256; x++) {
|
|
data[cellIndex(x, y, z0)] = colors[4];
|
|
data[cellIndex(x, y, z1)] = colors[5];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return byteArray;
|
|
}
|
|
|
|
static VolumeTextureData::AsyncLoaderData loadVolume(const VolumeTextureData::AsyncLoaderData &input)
|
|
{
|
|
QByteArray imageDataSource;
|
|
|
|
if (input.source == QUrl("file:///default_helix")) {
|
|
imageDataSource = createBuiltinVolume(ExampleId::Helix);
|
|
} else if (input.source == QUrl("file:///default_box")) {
|
|
imageDataSource = createBuiltinVolume(ExampleId::Box);
|
|
} else if (input.source == QUrl("file:///default_colormap")) {
|
|
imageDataSource = createBuiltinVolume(ExampleId::Colormap);
|
|
} else {
|
|
// NOTE: we always assume a local file is opened
|
|
QFile file(input.source.toLocalFile());
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
qWarning() << "Could not open file: " << file.fileName();
|
|
auto result = input;
|
|
result.success = false;
|
|
return result;
|
|
}
|
|
|
|
imageDataSource = file.readAll();
|
|
file.close();
|
|
}
|
|
|
|
QByteArray imageData;
|
|
|
|
// We scale the values to uint8_t data size
|
|
if (input.dataType == "uint8") {
|
|
imageData = imageDataSource;
|
|
} else if (input.dataType == "uint16") {
|
|
convertData<uint16_t>(imageData, imageDataSource);
|
|
} else if (input.dataType == "int16") {
|
|
convertData<int16_t>(imageData, imageDataSource);
|
|
} else if (input.dataType == "float32") {
|
|
convertData<float>(imageData, imageDataSource);
|
|
} else if (input.dataType == "float64") {
|
|
convertData<double>(imageData, imageDataSource);
|
|
} else {
|
|
qWarning() << "Unknown data type, assuming uint8";
|
|
imageData = imageDataSource;
|
|
}
|
|
|
|
// If our source data is smaller than expected we need to expand the texture
|
|
// and fill with something
|
|
qsizetype dataSize = input.depth * input.width * input.height;
|
|
if (imageData.size() < dataSize) {
|
|
imageData.resize(dataSize, '0');
|
|
}
|
|
|
|
auto result = input;
|
|
result.volumeData = imageData;
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
class Worker : public QThread
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
Worker(VolumeTextureData *parent, const VolumeTextureData::AsyncLoaderData &loaderData)
|
|
: QThread(parent), m_loaderData(loaderData)
|
|
{
|
|
}
|
|
void run() override { emit resultReady(loadVolume(m_loaderData)); }
|
|
|
|
signals:
|
|
void resultReady(const VolumeTextureData::AsyncLoaderData result);
|
|
|
|
private:
|
|
VolumeTextureData::AsyncLoaderData m_loaderData;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
VolumeTextureData::VolumeTextureData()
|
|
{
|
|
// Load a volume by default so we have something to render to avoid crashes
|
|
m_source = QUrl("file:///default_colormap");
|
|
m_width = 256;
|
|
m_height = 256;
|
|
m_depth = 256;
|
|
m_dataType = "uint8";
|
|
auto result = loadVolume(AsyncLoaderData { m_source, m_width, m_height, m_depth, m_dataType });
|
|
setFormat(Format::R8);
|
|
setTextureData(result.volumeData);
|
|
setSize(QSize(m_width, m_height));
|
|
QQuick3DTextureData::setDepth(m_depth);
|
|
}
|
|
|
|
VolumeTextureData::~VolumeTextureData()
|
|
{
|
|
if (m_worker) {
|
|
m_worker->quit();
|
|
m_worker->wait();
|
|
delete m_worker;
|
|
}
|
|
}
|
|
|
|
QUrl VolumeTextureData::source() const
|
|
{
|
|
return m_source;
|
|
}
|
|
|
|
void VolumeTextureData::setSource(const QUrl &newSource)
|
|
{
|
|
if (m_source == newSource)
|
|
return;
|
|
|
|
m_source = newSource;
|
|
if (!m_isLoading && !m_source.isEmpty())
|
|
loadAsync(m_source, m_width, m_height, m_depth, m_dataType);
|
|
emit sourceChanged();
|
|
}
|
|
|
|
qsizetype VolumeTextureData::width() const
|
|
{
|
|
return m_width;
|
|
}
|
|
|
|
void VolumeTextureData::setWidth(qsizetype newWidth)
|
|
{
|
|
if (m_width == newWidth)
|
|
return;
|
|
|
|
m_width = newWidth;
|
|
updateTextureDimensions();
|
|
emit widthChanged();
|
|
}
|
|
|
|
qsizetype VolumeTextureData::height() const
|
|
{
|
|
return m_height;
|
|
}
|
|
|
|
void VolumeTextureData::setHeight(qsizetype newHeight)
|
|
{
|
|
if (m_height == newHeight)
|
|
return;
|
|
|
|
m_height = newHeight;
|
|
updateTextureDimensions();
|
|
emit heightChanged();
|
|
}
|
|
|
|
qsizetype VolumeTextureData::depth() const
|
|
{
|
|
return m_depth;
|
|
}
|
|
|
|
void VolumeTextureData::setDepth(qsizetype newDepth)
|
|
{
|
|
if (m_depth == newDepth)
|
|
return;
|
|
|
|
m_depth = newDepth;
|
|
updateTextureDimensions();
|
|
emit depthChanged();
|
|
}
|
|
|
|
QString VolumeTextureData::dataType() const
|
|
{
|
|
return m_dataType;
|
|
}
|
|
|
|
void VolumeTextureData::setDataType(const QString &newDataType)
|
|
{
|
|
if (m_dataType == newDataType)
|
|
return;
|
|
m_dataType = newDataType;
|
|
if (!m_isLoading && !m_source.isEmpty())
|
|
loadAsync(m_source, m_width, m_height, m_depth, m_dataType);
|
|
emit dataTypeChanged();
|
|
}
|
|
|
|
void VolumeTextureData::updateTextureDimensions()
|
|
{
|
|
if (m_width * m_height * m_depth > m_currentDataSize)
|
|
return;
|
|
|
|
setSize(QSize(m_width, m_height));
|
|
QQuick3DTextureData::setDepth(m_depth);
|
|
}
|
|
|
|
void VolumeTextureData::loadAsync(QUrl source, qsizetype width, qsizetype height, qsizetype depth, QString dataType)
|
|
{
|
|
loaderData.source = source;
|
|
loaderData.width = width;
|
|
loaderData.height = height;
|
|
loaderData.depth = depth;
|
|
loaderData.dataType = dataType;
|
|
|
|
if (m_isLoading) {
|
|
m_isAborting = true;
|
|
return;
|
|
}
|
|
|
|
m_isLoading = true;
|
|
initWorker();
|
|
}
|
|
|
|
void VolumeTextureData::initWorker()
|
|
{
|
|
Q_ASSERT(!m_worker || !m_worker->isRunning());
|
|
delete m_worker;
|
|
m_worker = new Worker(this, loaderData);
|
|
connect(m_worker, &Worker::resultReady, this, &VolumeTextureData::handleResults);
|
|
m_worker->start();
|
|
Q_ASSERT(m_worker->isRunning());
|
|
}
|
|
|
|
void VolumeTextureData::handleResults(AsyncLoaderData result)
|
|
{
|
|
m_worker->quit();
|
|
m_worker->wait();
|
|
|
|
if (m_isAborting) {
|
|
m_isAborting = false;
|
|
initWorker();
|
|
return;
|
|
}
|
|
|
|
if (!result.success) {
|
|
emit loadFailed(result.source, result.width, result.height, result.depth, result.dataType);
|
|
}
|
|
|
|
m_currentDataSize = result.volumeData.size();
|
|
|
|
setSize(QSize(m_width, m_height));
|
|
QQuick3DTextureData::setDepth(m_depth);
|
|
setFormat(Format::R8);
|
|
setTextureData(result.volumeData);
|
|
updateTextureDimensions();
|
|
|
|
setWidth(result.width);
|
|
setHeight(result.height);
|
|
setDepth(result.depth);
|
|
setDataType(result.dataType);
|
|
setSource(result.source);
|
|
|
|
emit loadSucceeded(result.source, result.width, result.height, result.depth, result.dataType);
|
|
m_isLoading = false;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#include "volumetexturedata.moc"
|