diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b6534d..0821a8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/imagescrollareagl.cpp b/imagescrollareagl.cpp index 94d288b..81e6572 100644 --- a/imagescrollareagl.cpp +++ b/imagescrollareagl.cpp @@ -14,9 +14,12 @@ #include #include #include +#include +#include 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 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(new QOpenGLShaderProgram); @@ -588,6 +601,14 @@ void ImageWidget::initializeGL() m_transferOptions = std::unique_ptr(new QOpenGLPixelTransferOptions); m_transferOptions->setAlignment(1); + m_lut = std::make_unique(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); } diff --git a/imagescrollareagl.h b/imagescrollareagl.h index 7b90e5e..5b07b34 100644 --- a/imagescrollareagl.h +++ b/imagescrollareagl.h @@ -42,6 +42,7 @@ class ImageWidget : public QOpenGLWidget std::unique_ptr m_vaoThumb; std::unique_ptr m_transferOptions; std::unique_ptr m_thumbnailTexture; + std::unique_ptr m_lut; GLuint m_debayerTex = 0; std::shared_ptr m_rawImage; std::shared_ptr m_wcs; diff --git a/loadrunable.cpp b/loadrunable.cpp index 8fd1683..c80427d 100644 --- a/loadrunable.cpp +++ b/loadrunable.cpp @@ -13,10 +13,7 @@ #include #include "rawimage.h" #include "starfit.h" - -#ifdef COLOR_MANAGMENT -#include -#endif +#include 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(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; + if(m_thumbnail) + QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr, rawImage)); + else + QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(std::shared_ptr, rawImage), Q_ARG(ImageInfoData, ImageInfoData())); } } @@ -497,10 +505,6 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr #include -/*float h2f(unsigned short h) -{ - unsigned int - return f; -}*/ - int main(int argc, char *argv[]) { #ifdef __linux__ diff --git a/rawimage.cpp b/rawimage.cpp index 2d71adf..b051774 100644 --- a/rawimage.cpp +++ b/rawimage.cpp @@ -3,6 +3,7 @@ #include #include #include +#include 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); @@ -828,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(icc.begin(), icc.end()); +} + +void RawImage::setICCProfile(const LibXISF::ByteArray &icc) +{ + if(icc.size()) + m_iccProfile = std::vector(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 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 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 &RawImage::getLUT() const +{ + return m_lut; +} diff --git a/rawimage.h b/rawimage.h index 81a54ad..7482adc 100644 --- a/rawimage.h +++ b/rawimage.h @@ -1,13 +1,14 @@ #ifndef RAWIMAGE_H #define RAWIMAGE_H +#include "libxisf.h" #include -#include #include #include #include #include #include +#include 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 m_iccProfile; + std::vector 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 split() const; bool valid() const; + void setICCProfile(const QByteArray &icc); + void setICCProfile(const LibXISF::ByteArray &icc); + void convertTosRGB(); + void generateLUT(); + const std::vector& getLUT() const; + }; //Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr); diff --git a/shaders/image.frag b/shaders/image.frag index f643f61..aabf6ba 100644 --- a/shaders/image.frag +++ b/shaders/image.frag @@ -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);