Use lcms2 for color profiles

This commit is contained in:
2024-08-24 16:37:06 +02:00
parent ff5053b626
commit bc29dc7d34
8 changed files with 183 additions and 25 deletions
+1 -1
View File
@@ -84,7 +84,7 @@ if(UNIX AND NOT APPLE)
target_include_directories(tenmon PRIVATE ${GIO_INCLUDE_DIRS}) target_include_directories(tenmon PRIVATE ${GIO_INCLUDE_DIRS})
endif() 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) if(APPLE)
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation") target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
elseif(UNIX) elseif(UNIX)
+28 -7
View File
@@ -14,9 +14,12 @@
#include <QFileInfo> #include <QFileInfo>
#include <cmath> #include <cmath>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QFloat16>
#include <lcms2.h>
int FILTERING = 1; int FILTERING = 1;
bool OpenGLES = false; bool OpenGLES = false;
const int LUT_SIZE = 32;
struct RawImageType struct RawImageType
{ {
@@ -32,7 +35,7 @@ RawImageType getRawImageType(const RawImage *img)
{ {
case RawImage::UINT8: case RawImage::UINT8:
if(img->channels() >= 3) if(img->channels() >= 3)
type.textureFormat = QOpenGLTexture::SRGB8_Alpha8; type.textureFormat = QOpenGLTexture::RGBA8_UNorm;
else else
type.textureFormat = QOpenGLTexture::R8_UNorm; type.textureFormat = QOpenGLTexture::R8_UNorm;
type.dataType = QOpenGLTexture::UInt8; type.dataType = QOpenGLTexture::UInt8;
@@ -123,9 +126,14 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
if(!m_image)return; if(!m_image)return;
RawImageType rawImageType = getRawImageType(image.get()); RawImageType rawImageType = getRawImageType(image.get());
m_srgb = rawImageType.textureFormat == QOpenGLTexture::SRGB8_Alpha8; m_srgb = image->getLUT().size() > 0;
m_bwImg = image->channels() == 1; 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; QElapsedTimer timer;
timer.start(); timer.start();
m_image->destroy(); m_image->destroy();
@@ -216,6 +224,7 @@ void ImageWidget::allocateThumbnails(const QStringList &paths)
m_thumbnailTexture->setFormat(QOpenGLTexture::RGBA16F); m_thumbnailTexture->setFormat(QOpenGLTexture::RGBA16F);
m_thumbnailTexture->setSize(THUMB_SIZE, THUMB_SIZE); m_thumbnailTexture->setSize(THUMB_SIZE, THUMB_SIZE);
m_thumbnailTexture->setLayers(std::min((int)paths.size(), m_maxArrayLayers)); m_thumbnailTexture->setLayers(std::min((int)paths.size(), m_maxArrayLayers));
m_thumbnailTexture->setWrapMode(QOpenGLTexture::ClampToEdge);
m_thumbnailTexture->allocateStorage(); m_thumbnailTexture->allocateStorage();
} }
@@ -364,14 +373,16 @@ void ImageWidget::paintGL()
QMatrix4x4 mvp; QMatrix4x4 mvp;
mvp.ortho(rect()); mvp.ortho(rect());
m_thumbnailProgram->setUniformValue("mvp", mvp); 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 w = width()/THUMB_SIZE_BORDER;
const int off = (THUMB_SIZE_BORDER - THUMB_SIZE) / 2; const int off = (THUMB_SIZE_BORDER - THUMB_SIZE) / 2;
int start = std::max((int)(m_dy / THUMB_SIZE_BORDER_Y * w - w), 0); 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); 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++) for(int i=start; i < end; i++)
{ {
float x = (i % w) * THUMB_SIZE_BORDER; 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("false_color", m_falseColor && m_bwImg);
m_program->setUniformValue("invert", m_invert); m_program->setUniformValue("invert", m_invert);
m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1); 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); m_program->setUniformValue("srgb", m_srgb);
#endif
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_vao->release(); m_vao->release();
} }
@@ -462,7 +472,9 @@ void ImageWidget::initializeGL()
{ {
src = "#version 300 es\n" src = "#version 300 es\n"
"precision highp float;\n" "precision highp float;\n"
"precision highp sampler2D;\n"
"precision highp sampler2DArray;\n" "precision highp sampler2DArray;\n"
"precision highp sampler3D;\n"
"#line 1\n"; "#line 1\n";
} }
else else
@@ -525,6 +537,7 @@ void ImageWidget::initializeGL()
m_program->enableAttributeArray("qt_MultiTexCoord0"); m_program->enableAttributeArray("qt_MultiTexCoord0");
m_program->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4); m_program->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4);
m_program->setUniformValue("qt_Texture0", (GLuint)0); m_program->setUniformValue("qt_Texture0", (GLuint)0);
m_program->setUniformValue("lut_table", (GLuint)2);
m_program->setUniformValue("scale", 1.0f, 0.0f); m_program->setUniformValue("scale", 1.0f, 0.0f);
m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram); 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 = std::unique_ptr<QOpenGLPixelTransferOptions>(new QOpenGLPixelTransferOptions);
m_transferOptions->setAlignment(1); 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) if(m_rawImage)
setImage(m_rawImage, m_currentImg); setImage(m_rawImage, m_currentImg);
} }
+1
View File
@@ -42,6 +42,7 @@ class ImageWidget : public QOpenGLWidget
std::unique_ptr<QOpenGLVertexArrayObject> m_vaoThumb; std::unique_ptr<QOpenGLVertexArrayObject> m_vaoThumb;
std::unique_ptr<QOpenGLPixelTransferOptions> m_transferOptions; std::unique_ptr<QOpenGLPixelTransferOptions> m_transferOptions;
std::unique_ptr<QOpenGLTexture> m_thumbnailTexture; std::unique_ptr<QOpenGLTexture> m_thumbnailTexture;
std::unique_ptr<QOpenGLTexture> m_lut;
GLuint m_debayerTex = 0; GLuint m_debayerTex = 0;
std::shared_ptr<RawImage> m_rawImage; std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSData> m_wcs; std::shared_ptr<WCSData> m_wcs;
+13 -9
View File
@@ -13,10 +13,7 @@
#include <libxisf.h> #include <libxisf.h>
#include "rawimage.h" #include "rawimage.h"
#include "starfit.h" #include "starfit.h"
#include <lcms2.h>
#ifdef COLOR_MANAGMENT
#include <QColorSpace>
#endif
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) : LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
m_file(file), 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); image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount()); std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
image->setICCProfile(tmpImage.iccProfile());
return true; return true;
} }
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4) else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{ {
image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type); image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
image->setICCProfile(tmpImage.iccProfile());
return true; return true;
} }
return false; return false;
@@ -380,6 +379,10 @@ void LoadRunable::run()
{ {
rawImage->convertToGLFormat(); rawImage->convertToGLFormat();
timer.start(); timer.start();
rawImage->generateLUT();
qDebug() << "generate LUT" << timer.restart();
//rawImage->convertTosRGB();
//qDebug() << "convert" << timer.restart();
rawImage->calcStats(); rawImage->calcStats();
const RawImage::Stats &stats = rawImage->imageStats(); const RawImage::Stats &stats = rawImage->imageStats();
qDebug() << "image stats" << timer.restart(); qDebug() << "image stats" << timer.restart();
@@ -426,7 +429,12 @@ void LoadRunable::run()
} }
catch(std::exception e) 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 else
{ {
QImage img(path); 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()); ExifData *exif = exif_data_new_from_file(path.toLocal8Bit().constData());
info.info.append({QObject::tr("Width"), QString::number(img.width())}); info.info.append({QObject::tr("Width"), QString::number(img.width())});
-6
View File
@@ -4,12 +4,6 @@
#include <QTranslator> #include <QTranslator>
#include <stdlib.h> #include <stdlib.h>
/*float h2f(unsigned short h)
{
unsigned int
return f;
}*/
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
#ifdef __linux__ #ifdef __linux__
+121
View File
@@ -3,6 +3,7 @@
#include <cstring> #include <cstring>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QFloat16> #include <QFloat16>
#include <lcms2.h>
using F16 = qfloat16; using F16 = qfloat16;
@@ -79,6 +80,7 @@ RawImage::RawImage(RawImage &&d)
RawImage::RawImage(const QImage &img) RawImage::RawImage(const QImage &img)
{ {
qDebug() << img; qDebug() << img;
setICCProfile(img.colorSpace().iccProfile());
if(img.format() == QImage::Format_RGBX8888) if(img.format() == QImage::Format_RGBX8888)
{ {
allocate(img.width(), img.height(), 3, UINT8); allocate(img.width(), img.height(), 3, UINT8);
@@ -828,3 +830,122 @@ bool RawImage::valid() const
{ {
return m_width > 0 && m_height > 0; 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;
}
+10 -1
View File
@@ -1,13 +1,14 @@
#ifndef RAWIMAGE_H #ifndef RAWIMAGE_H
#define RAWIMAGE_H #define RAWIMAGE_H
#include "libxisf.h"
#include <vector> #include <vector>
#include <algorithm>
#include <memory> #include <memory>
#include <stdint.h> #include <stdint.h>
#include <math.h> #include <math.h>
#include <memory.h> #include <memory.h>
#include <QImage> #include <QImage>
#include <QColorSpace>
extern int THUMB_SIZE; extern int THUMB_SIZE;
extern int THUMB_SIZE_BORDER; extern int THUMB_SIZE_BORDER;
@@ -73,6 +74,8 @@ protected:
float m_thumbAspect = 0.0; float m_thumbAspect = 0.0;
Stats m_stats; Stats m_stats;
bool m_planar = false; 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); void allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type);
public: public:
RawImage(); RawImage();
@@ -112,6 +115,12 @@ public:
static size_t typeSize(DataType type); static size_t typeSize(DataType type);
std::vector<RawImage> split() const; std::vector<RawImage> split() const;
bool valid() 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); //Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr);
+9 -1
View File
@@ -1,4 +1,5 @@
uniform sampler2D qt_Texture0; uniform sampler2D qt_Texture0;
uniform sampler3D lut_table;
uniform vec3 mtf_param[3]; uniform vec3 mtf_param[3];
uniform vec2 unit_scale; uniform vec2 unit_scale;
uniform bool bw; uniform bool bw;
@@ -151,7 +152,14 @@ void main(void)
color.rgb = mix(checker(), color.rgb, color.a); 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)))) 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); color = vec4(0.0, 0.0, 0.0, 1.0);