Compare commits

...

2 Commits

Author SHA1 Message Date
bc29dc7d34 Use lcms2 for color profiles 2024-08-24 16:37:06 +02:00
ff5053b626 Added missing case FLOAT16 2024-08-22 19:10:26 +02:00
8 changed files with 187 additions and 25 deletions

@ -84,7 +84,7 @@ if(UNIX AND NOT APPLE)
target_include_directories(tenmon PRIVATE ${GIO_INCLUDE_DIRS})
endif()
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} XISF)
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} XISF lcms2)
if(APPLE)
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
elseif(UNIX)

@ -14,9 +14,12 @@
#include <QFileInfo>
#include <cmath>
#include <QElapsedTimer>
#include <QFloat16>
#include <lcms2.h>
int FILTERING = 1;
bool OpenGLES = false;
const int LUT_SIZE = 32;
struct RawImageType
{
@ -32,7 +35,7 @@ RawImageType getRawImageType(const RawImage *img)
{
case RawImage::UINT8:
if(img->channels() >= 3)
type.textureFormat = QOpenGLTexture::SRGB8_Alpha8;
type.textureFormat = QOpenGLTexture::RGBA8_UNorm;
else
type.textureFormat = QOpenGLTexture::R8_UNorm;
type.dataType = QOpenGLTexture::UInt8;
@ -123,9 +126,14 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
if(!m_image)return;
RawImageType rawImageType = getRawImageType(image.get());
m_srgb = rawImageType.textureFormat == QOpenGLTexture::SRGB8_Alpha8;
m_srgb = image->getLUT().size() > 0;
m_bwImg = image->channels() == 1;
if(m_srgb)
{
m_lut->setData(0, 0, 0, LUT_SIZE, LUT_SIZE, LUT_SIZE, 0, QOpenGLTexture::RGBA, QOpenGLTexture::RGBA, QOpenGLTexture::Float16, image->getLUT().data());
}
QElapsedTimer timer;
timer.start();
m_image->destroy();
@ -216,6 +224,7 @@ void ImageWidget::allocateThumbnails(const QStringList &paths)
m_thumbnailTexture->setFormat(QOpenGLTexture::RGBA16F);
m_thumbnailTexture->setSize(THUMB_SIZE, THUMB_SIZE);
m_thumbnailTexture->setLayers(std::min((int)paths.size(), m_maxArrayLayers));
m_thumbnailTexture->setWrapMode(QOpenGLTexture::ClampToEdge);
m_thumbnailTexture->allocateStorage();
}
@ -364,14 +373,16 @@ void ImageWidget::paintGL()
QMatrix4x4 mvp;
mvp.ortho(rect());
m_thumbnailProgram->setUniformValue("mvp", mvp);
fx->glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, m_thumbnailCount);
m_vaoThumb->release();
QPainter painter(this);
const int w = width()/THUMB_SIZE_BORDER;
const int off = (THUMB_SIZE_BORDER - THUMB_SIZE) / 2;
int start = std::max((int)(m_dy / THUMB_SIZE_BORDER_Y * w - w), 0);
int end = std::min((int)(m_dy + m_height) / THUMB_SIZE_BORDER_Y * w + w, m_thumbnailCount);
fx->glDrawArraysInstanced(GL_TRIANGLE_STRIP, start*4, 4, (end - start) * 4);
m_vaoThumb->release();
QPainter painter(this);
for(int i=start; i < end; i++)
{
float x = (i % w) * THUMB_SIZE_BORDER;
@ -424,9 +435,8 @@ void ImageWidget::paintGL()
m_program->setUniformValue("false_color", m_falseColor && m_bwImg);
m_program->setUniformValue("invert", m_invert);
m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1);
#ifdef COLOR_MANAGMENT
m_program->setUniformValue("lut_table", 2);
m_program->setUniformValue("srgb", m_srgb);
#endif
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_vao->release();
}
@ -462,7 +472,9 @@ void ImageWidget::initializeGL()
{
src = "#version 300 es\n"
"precision highp float;\n"
"precision highp sampler2D;\n"
"precision highp sampler2DArray;\n"
"precision highp sampler3D;\n"
"#line 1\n";
}
else
@ -525,6 +537,7 @@ void ImageWidget::initializeGL()
m_program->enableAttributeArray("qt_MultiTexCoord0");
m_program->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4);
m_program->setUniformValue("qt_Texture0", (GLuint)0);
m_program->setUniformValue("lut_table", (GLuint)2);
m_program->setUniformValue("scale", 1.0f, 0.0f);
m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
@ -588,6 +601,14 @@ void ImageWidget::initializeGL()
m_transferOptions = std::unique_ptr<QOpenGLPixelTransferOptions>(new QOpenGLPixelTransferOptions);
m_transferOptions->setAlignment(1);
m_lut = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target3D);
m_lut->setSize(LUT_SIZE, LUT_SIZE, LUT_SIZE);
m_lut->setMipLevelRange(0, 0);
m_lut->setFormat(QOpenGLTexture::TextureFormat::RGBA16F);
m_lut->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
m_lut->allocateStorage();
m_lut->bind(2);
if(m_rawImage)
setImage(m_rawImage, m_currentImg);
}

@ -42,6 +42,7 @@ class ImageWidget : public QOpenGLWidget
std::unique_ptr<QOpenGLVertexArrayObject> m_vaoThumb;
std::unique_ptr<QOpenGLPixelTransferOptions> m_transferOptions;
std::unique_ptr<QOpenGLTexture> m_thumbnailTexture;
std::unique_ptr<QOpenGLTexture> m_lut;
GLuint m_debayerTex = 0;
std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSData> m_wcs;

@ -13,10 +13,7 @@
#include <libxisf.h>
#include "rawimage.h"
#include "starfit.h"
#ifdef COLOR_MANAGMENT
#include <QColorSpace>
#endif
#include <lcms2.h>
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
m_file(file),
@ -339,11 +336,13 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
{
image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
image->setICCProfile(tmpImage.iccProfile());
return true;
}
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{
image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
image->setICCProfile(tmpImage.iccProfile());
return true;
}
return false;
@ -380,6 +379,10 @@ void LoadRunable::run()
{
rawImage->convertToGLFormat();
timer.start();
rawImage->generateLUT();
qDebug() << "generate LUT" << timer.restart();
//rawImage->convertTosRGB();
//qDebug() << "convert" << timer.restart();
rawImage->calcStats();
const RawImage::Stats &stats = rawImage->imageStats();
qDebug() << "image stats" << timer.restart();
@ -426,7 +429,12 @@ void LoadRunable::run()
}
catch(std::exception e)
{
qDebug() << m_file << e.what();
qDebug() << m_file << e.what();
std::shared_ptr<RawImage> rawImage;
if(m_thumbnail)
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
else
QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage), Q_ARG(ImageInfoData, ImageInfoData()));
}
}
@ -497,10 +505,6 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImag
else
{
QImage img(path);
#ifdef COLOR_MANAGMENT
if(img.colorSpace().isValid() && img.colorSpace() != QColorSpace::SRgb)
img.convertToColorSpace(QColorSpace::SRgb);
#endif
ExifData *exif = exif_data_new_from_file(path.toLocal8Bit().constData());
info.info.append({QObject::tr("Width"), QString::number(img.width())});

@ -4,12 +4,6 @@
#include <QTranslator>
#include <stdlib.h>
/*float h2f(unsigned short h)
{
unsigned int
return f;
}*/
int main(int argc, char *argv[])
{
#ifdef __linux__

@ -3,6 +3,7 @@
#include <cstring>
#include <QElapsedTimer>
#include <QFloat16>
#include <lcms2.h>
using F16 = qfloat16;
@ -79,6 +80,7 @@ RawImage::RawImage(RawImage &&d)
RawImage::RawImage(const QImage &img)
{
qDebug() << img;
setICCProfile(img.colorSpace().iccProfile());
if(img.format() == QImage::Format_RGBX8888)
{
allocate(img.width(), img.height(), 3, UINT8);
@ -755,6 +757,9 @@ std::shared_ptr<RawImage> RawImage::fromPlanar(const void *pixels, uint32_t w, u
convert(static_cast<const uint16_t*>(pixels), static_cast<uint16_t*>(image->data()), UINT16_MAX);
#endif
break;
case FLOAT16:
convert(static_cast<const F16*>(pixels), static_cast<F16*>(image->data()), (F16)1.0f);
break;
case UINT32:
#ifdef __SSE2__
if(ch==3)
@ -804,6 +809,7 @@ std::vector<RawImage> RawImage::split() const
case UINT8:
extract(static_cast<const uint8_t*>(data()), static_cast<uint8_t*>(planes[i].data()), i);
break;
case FLOAT16:
case UINT16:
extract(static_cast<const uint16_t*>(data()), static_cast<uint16_t*>(planes[i].data()), i);
break;
@ -824,3 +830,122 @@ bool RawImage::valid() const
{
return m_width > 0 && m_height > 0;
}
void RawImage::setICCProfile(const QByteArray &icc)
{
if(icc.size())
m_iccProfile = std::vector<uint8_t>(icc.begin(), icc.end());
}
void RawImage::setICCProfile(const LibXISF::ByteArray &icc)
{
if(icc.size())
m_iccProfile = std::vector<uint8_t>(icc.data(), icc.data() + icc.size());
}
void RawImage::convertTosRGB()
{
if(m_channels == 1 || m_iccProfile.empty())
return;
cmsUInt32Number type = TYPE_RGBA_8;
switch(m_type)
{
case UINT8:
type = TYPE_RGBA_8;
break;
case UINT16:
type = TYPE_RGBA_16;
break;
case FLOAT16:
type = TYPE_RGBA_HALF_FLT;
break;
case FLOAT32:
type = TYPE_RGBA_FLT;
break;
default:
return;
}
cmsHPROFILE inProfile = cmsOpenProfileFromMem(m_iccProfile.data(), m_iccProfile.size());
cmsHPROFILE outProfile = cmsCreate_sRGBProfile();
if(inProfile && outProfile)
{
cmsHTRANSFORM transform = cmsCreateTransform(inProfile, type, outProfile, type, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA);
std::unique_ptr<PixelType[]> tmp = std::move(m_pixels);
allocate(m_width, m_height, m_channels, m_type);
if(transform)
{
cmsDoTransform(transform, tmp.get(), m_pixels.get(), size());
cmsDeleteTransform(transform);
}
else
{
qDebug() << "Failed to create color transform";
}
}
else
{
qDebug() << "Failed to open icc profile";
}
cmsCloseProfile(inProfile);
cmsCloseProfile(outProfile);
}
void RawImage::generateLUT()
{
if(m_channels == 1 || m_iccProfile.empty())
return;
const int LUT_SIZE = 32;
const float LUT_SIZEF = LUT_SIZE - 1;
std::vector<qfloat16> lut(LUT_SIZE * LUT_SIZE * LUT_SIZE * 4);
m_lut.resize(lut.size());
for(int z = 0; z < LUT_SIZE; z++)
{
for(int y = 0; y < LUT_SIZE; y++)
{
qfloat16 *line = &lut[(z*LUT_SIZE*LUT_SIZE + y*LUT_SIZE) * 4];
for(int x = 0; x < LUT_SIZE; x++)
{
line[x*4 + 0] = x / LUT_SIZEF;
line[x*4 + 1] = y / LUT_SIZEF;
line[x*4 + 2] = z / LUT_SIZEF;
line[x*4 + 3] = 1.0f;
}
}
}
cmsHPROFILE inProfile = cmsOpenProfileFromMem(m_iccProfile.data(), m_iccProfile.size());
cmsHPROFILE outProfile = cmsCreate_sRGBProfile();
if(inProfile && outProfile)
{
cmsHTRANSFORM transform = cmsCreateTransform(inProfile, TYPE_RGBA_HALF_FLT, outProfile, TYPE_RGBA_HALF_FLT, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA);
if(transform)
{
cmsDoTransform(transform, &lut[0], &m_lut[0], lut.size()/4);
cmsDeleteTransform(transform);
}
else
{
qDebug() << "Failed to create color transform";
m_lut.clear();
}
}
else
{
qDebug() << "Failed to open icc profile";
m_lut.clear();
}
cmsCloseProfile(inProfile);
cmsCloseProfile(outProfile);
}
const std::vector<uint16_t> &RawImage::getLUT() const
{
return m_lut;
}

@ -1,13 +1,14 @@
#ifndef RAWIMAGE_H
#define RAWIMAGE_H
#include "libxisf.h"
#include <vector>
#include <algorithm>
#include <memory>
#include <stdint.h>
#include <math.h>
#include <memory.h>
#include <QImage>
#include <QColorSpace>
extern int THUMB_SIZE;
extern int THUMB_SIZE_BORDER;
@ -73,6 +74,8 @@ protected:
float m_thumbAspect = 0.0;
Stats m_stats;
bool m_planar = false;
std::vector<uint8_t> m_iccProfile;
std::vector<uint16_t> m_lut;// actually qfloat16
void allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type);
public:
RawImage();
@ -112,6 +115,12 @@ public:
static size_t typeSize(DataType type);
std::vector<RawImage> split() const;
bool valid() const;
void setICCProfile(const QByteArray &icc);
void setICCProfile(const LibXISF::ByteArray &icc);
void convertTosRGB();
void generateLUT();
const std::vector<uint16_t>& getLUT() const;
};
//Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr);

@ -1,4 +1,5 @@
uniform sampler2D qt_Texture0;
uniform sampler3D lut_table;
uniform vec3 mtf_param[3];
uniform vec2 unit_scale;
uniform bool bw;
@ -151,7 +152,14 @@ void main(void)
color.rgb = mix(checker(), color.rgb, color.a);
if(srgb)color.rgb = Linear2sRGB(color.rgb);
if(srgb)
{
color.rgb *= 31.0 / 32.0;
color.rgb += 0.5 / 32.0;
vec4 lut = texture(lut_table, vec3(color.rgb));
color.rgb = lut.rgb;
//color.rgb = Linear2sRGB(lut.rgb);
}
if(any(lessThan(qt_TexCoord0, vec2(0.0))) || any(greaterThan(qt_TexCoord0, vec2(1.0))))
color = vec4(0.0, 0.0, 0.0, 1.0);