diff --git a/.github/workflows/build-linux-QT5.yml b/.github/workflows/build-linux-QT5.yml deleted file mode 100644 index bb82f26d..00000000 --- a/.github/workflows/build-linux-QT5.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: build-linux-QT5 -on: - push: - branches: - - master - pull_request: - workflow_dispatch: - workflow_call: - -jobs: - build-linux-QT5: - strategy: - fail-fast: false - matrix: - os: [ubuntu-24.04] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - run: sudo apt update - - run: sudo apt install -y apt-utils build-essential wget qt5-qmake qt5-qmake-bin qt5-assistant qtbase5-dev qtmultimedia5-dev libqt5charts5 libqt5charts5-dev libqt5multimedia* libqt5datavisualization5-dev libqt5datavisualization5 libopencv-core-dev libopencv-dev libqwt-qt5-6 libqwt-qt5-dev libarmadillo-dev - - run: qmake DFTFringe_QT5.pro - #- uses: ammaraskar/gcc-problem-matcher@master - #- run: echo "::add-matcher::.github/matcher/uic_matcher.json" - - run: make -j4 - #- run: echo "::remove-matcher owner=uic-problem-matcher::" - diff --git a/.github/workflows/build-linux-clazy.yml b/.github/workflows/build-linux-clazy.yml index e4bed7ed..dbff8d1b 100644 --- a/.github/workflows/build-linux-clazy.yml +++ b/.github/workflows/build-linux-clazy.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v4 - run: | sudo apt update - sudo apt install -y clazy apt-utils build-essential wget qt6-base-dev-tools qt6-declarative-dev qt6-multimedia-dev libqt6charts6-dev libqt6datavisualization6-dev libqt6svg6-dev libopencv-core-dev libopencv-dev libqwt-qt5-6 libqwt-qt5-dev libarmadillo-dev libgl1-mesa-dev libglu1-mesa-dev + sudo apt install -y clazy apt-utils build-essential wget qt6-base-dev-tools qt6-declarative-dev qt6-multimedia-dev libqt6charts6-dev libqt6datavisualization6-dev libqt6svg6-dev libopencv-core-dev libopencv-dev libqwt-qt5-6 libqwt-qt5-dev libarmadillo-dev libraw-dev libgl1-mesa-dev libglu1-mesa-dev wget -O qwt-${{env.QWT_version}}.zip https://sourceforge.net/projects/qwt/files/qwt/${{env.QWT_version}}/qwt-${{env.QWT_version}}.zip/download?use_mirror=pilotfiber 7z x qwt-${{env.QWT_version}}.zip cd qwt-${{env.QWT_version}} diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index b483b24f..f3556792 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v4 - run: sudo apt update - - run: sudo apt install -y apt-utils build-essential wget qt6-base-dev-tools qt6-declarative-dev qt6-multimedia-dev libqt6charts6-dev libqt6datavisualization6-dev libqt6svg6-dev libopencv-core-dev libopencv-dev libqwt-qt5-6 libqwt-qt5-dev libarmadillo-dev libgl1-mesa-dev libglu1-mesa-dev bear + - run: sudo apt install -y apt-utils build-essential wget qt6-base-dev-tools qt6-declarative-dev qt6-multimedia-dev libqt6charts6-dev libqt6datavisualization6-dev libqt6svg6-dev libopencv-core-dev libopencv-dev libqwt-qt5-6 libqwt-qt5-dev libarmadillo-dev libraw-dev libgl1-mesa-dev libglu1-mesa-dev bear - run: wget -O qwt-${{env.QWT_version}}.zip https://sourceforge.net/projects/qwt/files/qwt/${{env.QWT_version}}/qwt-${{env.QWT_version}}.zip/download?use_mirror=pilotfiber - run: 7z x qwt-${{env.QWT_version}}.zip - run: cd qwt-${{env.QWT_version}} ; /usr/lib/qt6/bin/qmake diff --git a/.github/workflows/build-windows-QT5.yml b/.github/workflows/build-windows-QT5.yml index a64327b8..98d1d090 100644 --- a/.github/workflows/build-windows-QT5.yml +++ b/.github/workflows/build-windows-QT5.yml @@ -3,6 +3,8 @@ on: push: branches: - master + tags: + - 'v*' pull_request: workflow_dispatch: workflow_call: @@ -13,6 +15,7 @@ env: openCV_version: 4.6.0 QT_version: 5.15.2 lapack_version: 3.12.1 + libraw_version: 0.22.2 mingw_version: 810 python_version: 3.13 @@ -231,9 +234,60 @@ jobs: if: steps.cache-lapack.outputs.cache-hit != 'true' run: cmake --build ./build_armadillo -j4 + build-libraw: + runs-on: windows-latest + needs: cache-mingw-from-QT + steps: + # this will restore/cache everything depending on cache hit/miss + - name: cache LibRaw + uses: actions/cache/@v4 + id: cache-libraw + with: + path: | + build_libraw\install\include\libraw\* + build_libraw\install\lib\libraw.a + key: ${{ runner.os }}-libraw-${{env.libraw_version}}-mingw${{env.mingw_version}} + + # all what follows is only run on cache miss + + # restore cached minGW + - uses: actions/cache/restore@v4 + if: steps.cache-libraw.outputs.cache-hit != 'true' + id: cache-minGW + with: + path: Tools + key: ${{ runner.os }}-mingw${{env.mingw_version}}_64 + fail-on-cache-miss: true + - name: add minGW to path + if: steps.cache-libraw.outputs.cache-hit != 'true' + shell: bash + run: echo "${{github.workspace}}\Tools\mingw${{env.mingw_version}}_64\bin" >> $GITHUB_PATH + - name: install wget + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: choco install -y wget + - name: download LibRaw + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: wget -O LibRaw.tar.gz https://github.com/LibRaw/LibRaw/archive/refs/tags/${{env.libraw_version}}.tar.gz + # Extract in two steps: first gunzip to stdout, then untar from stdin. + # Must run in CMD because powershell corrupts pipes. + - name: extract archive + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: 7z x -so LibRaw.tar.gz | 7z x -si -ttar + shell: cmd + - name: build LibRaw static library + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: cd LibRaw-${{env.libraw_version}} ; mingw32-make -f Makefile.mingw library + - name: install LibRaw (headers + static lib) into build_libraw\install + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: | + mkdir -Force ".\build_libraw\install\include\libraw" | Out-Null + mkdir -Force ".\build_libraw\install\lib" | Out-Null + Copy-Item ".\LibRaw-${{env.libraw_version}}\libraw\*.h" -Destination ".\build_libraw\install\include\libraw" + Copy-Item ".\LibRaw-${{env.libraw_version}}\lib\libraw.a" -Destination ".\build_libraw\install\lib" + build-DFTFringe: runs-on: windows-latest - needs: [cache-mingw-from-QT, build-lapack-armadillo, build-QWT, build-openCV-with-QT] + needs: [cache-mingw-from-QT, build-lapack-armadillo, build-QWT, build-openCV-with-QT, build-libraw] steps: # because problem matcher will not work when chechout in a subfolder, @@ -281,6 +335,15 @@ jobs: qwt-${{env.QWT_version}}\lib\*.dll key: ${{ runner.os }}-QWT-${{env.QWT_version}} fail-on-cache-miss: true + # restore cached LibRaw + - uses: actions/cache/restore@v4 + id: cache-libraw + with: + path: | + build_libraw\install\include\libraw\* + build_libraw\install\lib\libraw.a + key: ${{ runner.os }}-libraw-${{env.libraw_version}}-mingw${{env.mingw_version}} + fail-on-cache-miss: true # restore cached openCV and QT - uses: actions/cache/restore@v4 id: cache-openCV-QT @@ -326,6 +389,7 @@ jobs: (Get-Content DFTFringe/DFTFringeInstaller/config/config.xml).replace('MY_AUTOMATED_VERSION_STRING', '${{env.WORKFLOW_VERSION}}') | Set-Content DFTFringe/DFTFringeInstaller/config/config.xml (Get-Content DFTFringe/DFTFringeInstaller/packages/com.githubdoe.DFTFringe/meta/package.xml).replace('MY_AUTOMATED_VERSION_STRING', '${{env.WORKFLOW_VERSION}}') | Set-Content DFTFringe/DFTFringeInstaller/packages/com.githubdoe.DFTFringe/meta/package.xml (Get-Content DFTFringe/DFTFringe.pro).replace('MY_AUTOMATED_VERSION_STRING', '${{env.WORKFLOW_VERSION}}') | Set-Content DFTFringe/DFTFringe.pro + (Get-Content DFTFringe/DFTFringe_QT5.pro).replace('MY_AUTOMATED_VERSION_STRING', '${{env.WORKFLOW_VERSION}}') | Set-Content DFTFringe/DFTFringe_QT5.pro - name: Put current date into a variable run: | $NOW=& Get-Date -format yyyy-MM-dd diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index ae3cd0ef..466ef5fd 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -13,6 +13,7 @@ env: openCV_version: 4.12.0 QT_version: 6.8.3 lapack_version: 3.12.1 + libraw_version: 0.22.2 mingw_version: 1310 python_version: 3.13 @@ -232,9 +233,60 @@ jobs: if: steps.cache-lapack.outputs.cache-hit != 'true' run: cmake --build ./build_armadillo -j4 + build-libraw: + runs-on: windows-latest + needs: cache-mingw-from-QT + steps: + # this will restore/cache everything depending on cache hit/miss + - name: cache LibRaw + uses: actions/cache/@v4 + id: cache-libraw + with: + path: | + build_libraw\install\include\libraw\* + build_libraw\install\lib\libraw.a + key: ${{ runner.os }}-libraw-${{env.libraw_version}}-mingw${{env.mingw_version}} + + # all what follows is only run on cache miss + + # restore cached minGW + - uses: actions/cache/restore@v4 + if: steps.cache-libraw.outputs.cache-hit != 'true' + id: cache-minGW + with: + path: Tools + key: ${{ runner.os }}-mingw${{env.mingw_version}}_64 + fail-on-cache-miss: true + - name: add minGW to path + if: steps.cache-libraw.outputs.cache-hit != 'true' + shell: bash + run: echo "${{github.workspace}}\Tools\mingw${{env.mingw_version}}_64\bin" >> $GITHUB_PATH + - name: install wget + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: choco install -y wget + - name: download LibRaw + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: wget -O LibRaw.tar.gz https://github.com/LibRaw/LibRaw/archive/refs/tags/${{env.libraw_version}}.tar.gz + # Extract in two steps: first gunzip to stdout, then untar from stdin. + # Must run in CMD because powershell corrupts pipes. + - name: extract archive + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: 7z x -so LibRaw.tar.gz | 7z x -si -ttar + shell: cmd + - name: build LibRaw static library + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: cd LibRaw-${{env.libraw_version}} ; mingw32-make -f Makefile.mingw library + - name: install LibRaw (headers + static lib) into build_libraw\install + if: steps.cache-libraw.outputs.cache-hit != 'true' + run: | + mkdir -Force ".\build_libraw\install\include\libraw" | Out-Null + mkdir -Force ".\build_libraw\install\lib" | Out-Null + Copy-Item ".\LibRaw-${{env.libraw_version}}\libraw\*.h" -Destination ".\build_libraw\install\include\libraw" + Copy-Item ".\LibRaw-${{env.libraw_version}}\lib\libraw.a" -Destination ".\build_libraw\install\lib" + build-DFTFringe: runs-on: windows-latest - needs: [cache-mingw-from-QT, build-lapack-armadillo, build-QWT, build-openCV-with-QT] + needs: [cache-mingw-from-QT, build-lapack-armadillo, build-QWT, build-openCV-with-QT, build-libraw] steps: # because problem matcher will not work when chechout in a subfolder, @@ -282,6 +334,15 @@ jobs: qwt-${{env.QWT_version}}\lib\*.dll key: ${{ runner.os }}-QWT-${{env.QWT_version}}-mingw${{env.mingw_version}} fail-on-cache-miss: true + # restore cached LibRaw + - uses: actions/cache/restore@v4 + id: cache-libraw + with: + path: | + build_libraw\install\include\libraw\* + build_libraw\install\lib\libraw.a + key: ${{ runner.os }}-libraw-${{env.libraw_version}}-mingw${{env.mingw_version}} + fail-on-cache-miss: true # restore cached openCV and QT - uses: actions/cache/restore@v4 id: cache-openCV-QT @@ -338,7 +399,7 @@ jobs: echo "${{env.NOW}}" (Get-Content DFTFringe/DFTFringeInstaller/packages/com.githubdoe.DFTFringe/meta/package.xml).replace('MY_AUTOMATED_DATE_STRING', '${{env.NOW}}') | Set-Content DFTFringe/DFTFringeInstaller/packages/com.githubdoe.DFTFringe/meta/package.xml - - run: cd DFTFringe ; ..\${{env.QT_version}}\mingw_64\bin\qmake.exe + - run: cd DFTFringe ; ..\${{env.QT_version}}\mingw_64\bin\qmake.exe DFTFringe.pro - run: echo "::add-matcher::.github/matcher/uic_matcher.json" - run: cd DFTFringe ; mingw32-make -j4 - run: echo "::remove-matcher owner=uic-problem-matcher::" diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 4e92b218..9b583323 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -29,7 +29,7 @@ jobs: # acually build the artifacts that will be uploaded to release call-build-windows: needs: check-semver - uses: ./.github/workflows/build-windows.yml + uses: ./.github/workflows/build-windows-QT5.yml # linux build is mainly here to check it builds. We have no acrtifact now. call-build-linux: permissions: @@ -45,7 +45,7 @@ jobs: # get artifact uploaded from build workflow - uses: actions/download-artifact@v4 with: - name: DFTFringe-windows-build-artifact + name: DFTFringe-windows-build-artifact-QT5 # create the GitHub release and upload the artifacts - name: publish Release uses: softprops/action-gh-release@v1 @@ -59,5 +59,5 @@ jobs: # an automated release note is generated based on PRs to master generate_release_notes: true files: | - DFTFringeInstaller_${{github.ref_name}}.exe + DFTFringeInstaller_QT5_${{github.ref_name}}.exe Z_DFTFringe.exe.debug diff --git a/BUILD_RAW_NOTES.md b/BUILD_RAW_NOTES.md new file mode 100644 index 00000000..2c8d422d --- /dev/null +++ b/BUILD_RAW_NOTES.md @@ -0,0 +1,53 @@ +# Компилиране на DFTFringe с RAW поддръжка (камерни файлове) + +Промените за четене на камерни RAW файлове (CR2/CR3/NEF/ARW/DNG/RW2 и др.) вече са +вградени в сорса. Този файл описва какво е нужно, за да се компилира на Windows. + +## Какво е променено +- `rawimage.h` / `rawimage.cpp` — нов модул, чете RAW чрез LibRaw → QImage. +- `igramarea.cpp` — `openImage()` разпознава RAW и го чете (вкл. distortion клона). +- `mainwindow.cpp` — RAW разширенията са добавени във файловите диалози. +- `DFTFringe.pro` — добавени са LibRaw include/lib и новите файлове. + +## Нужен toolchain (същият, с който е построен инсталираният релийз в C:\DFTFringe) +Инсталираният exe е Qt6 + MinGW-w64 (GCC, SEH) + OpenCV 4.12.0. + +1. **Qt6 с MinGW комплект** (Qt Online Installer) + модули: Qt Charts, Qt Data Visualization. + Дава `qmake`, `mingw32-make`, `g++`, Qt Creator. + +2. **Предварително построените зависимости**, които `DFTFringe.pro` (win32 секцията) + очаква като СЪСЕДНИ папки на сорса, т.е. в `D:\Pictures\Mirrors\`: + - `qwt-6.3.0` + - `build_armadillo` + - `build_openCV` (OpenCV 4.12.0, MinGW — libopencv_*4120) + - `build_lapack` + Тези липсват в момента. Взимат се от билд-настройката на DFTFringe проекта + (upstream repo-то документира/сваля точните версии). Без тях qmake няма да намери + пътищата в win32 секцията. + +3. **LibRaw** (НОВАТА зависимост), построена със същия MinGW, инсталирана в: + `D:\Pictures\Mirrors\build_libraw\install\` + - хедъри в `install\include\libraw\libraw.h` + - `install\bin\libraw.dll` + `libraw.dll.a` (import lib) + Ако я сложиш другаде — коригирай двата реда `libraw` в `DFTFringe.pro` (win32). + Най-лесно набавяне: MSYS2 `pacman -S mingw-w64-x86_64-libraw` (същия ABI) и копиране + на include/lib/dll, или билд от източник. + +## Компилиране +От Qt Creator: отвори `DFTFringe.pro`, избери MinGW kit-а, Build → Release. + +Или от команден ред (в среда с qmake/mingw в PATH): +``` +qmake DFTFringe.pro +mingw32-make release +``` + +## Пускане / разпространение +Копирай `libraw.dll` (и евентуалните ѝ зависимости) до новополучения `DFTFringe.exe`, +до вече наличните Qt6/OpenCV DLL-ове. Най-просто: сложи новия exe и `libraw.dll` в +`C:\DFTFringe\` (където са всички останали DLL-ове) и го пусни оттам. + +## Алтернатива без локална инсталация +DFTFringe upstream има GitHub Actions CI, който сам сглобява целия toolchain и +произвежда готов exe/инсталатор. Fork → приложи тези промени → добави LibRaw към CI +стъпката → push → свали построения артефакт. diff --git a/DFTFringe.pro b/DFTFringe.pro index e94e9cab..0d8b41bf 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -46,6 +46,8 @@ win32 { INCLUDEPATH += ..\qwt-6.3.0\src INCLUDEPATH += ..\build_armadillo\tmp\include INCLUDEPATH += ..\build_openCV\install\include + INCLUDEPATH += ..\build_libraw\install\include # LibRaw for camera RAW support + DEFINES += LIBRAW_NODLL # link LibRaw statically (no __declspec(dllimport)) LIBS += -L$$PWD\..\build_lapack\bin -llibblas LIBS += -L$$PWD\..\build_lapack\bin -lliblapack @@ -55,6 +57,8 @@ win32 { LIBS += -L$$PWD\..\build_openCV\install\x64\mingw\bin -llibopencv_highgui4120 LIBS += -L$$PWD\..\build_openCV\install\x64\mingw\bin -llibopencv_imgcodecs4120 LIBS += -L$$PWD\..\build_openCV\install\x64\mingw\bin -llibopencv_imgproc4120 + LIBS += -L$$PWD\..\build_libraw\install\lib -lraw # LibRaw (static) for camera RAW support + LIBS += -lws2_32 # Winsock: LibRaw needs htons/ntohs/htonl/ntohl on Windows LIBS += -ldbghelp # for SetUnhandledExceptionFilter LIBS += -lz # zip compression library needed for cnpy.cpp @@ -70,6 +74,7 @@ unix: !mac { INCLUDEPATH += /usr/include/opencv4 INCLUDEPATH += /usr/local/qwt-6.3.0/include + INCLUDEPATH += /usr/include/libraw QMAKE_RPATHDIR += /usr/local/qwt-6.3.0/lib @@ -82,6 +87,7 @@ unix: !mac { LIBS += -lopencv_imgcodecs LIBS += -lopencv_imgproc LIBS += -L/usr/local/qwt-6.3.0/lib -lqwt + LIBS += -lraw # LibRaw for camera RAW support LIBS += -lz # zip compression library needed for cnpy.cpp } @@ -114,7 +120,7 @@ macx { PKG_CONFIG_PATH = $$[QT_INSTALL_LIBS]/pkgconfig INCLUDEPATH += -I$$[QT_INSTALL_PLUGINS] LIBS += -L$$[QT_INSTALL_PLUGINS] - PKGCONFIG += armadillo opencv Qt5Qwt6 + PKGCONFIG += armadillo opencv Qt5Qwt6 libraw message(........QT_VERSION: $$[QT_VERSION]) message(.QT_INSTALL_PREFIX: $$[QT_INSTALL_PREFIX]) @@ -222,6 +228,7 @@ SOURCES += SingleApplication/singleapplication.cpp \ psiphasedisplay.cpp \ psitiltoptions.cpp \ punwrap.cpp \ + rawimage.cpp \ regionedittools.cpp \ rejectedwavefrontsdlg.cpp \ reportdlg.cpp \ @@ -344,6 +351,7 @@ HEADERS += bezier/bezier.h \ psiphasedisplay.h \ psitiltoptions.h \ punwrap.h \ + rawimage.h \ regionedittools.h \ rejectedwavefrontsdlg.h \ reportdlg.h \ diff --git a/DFTFringe_QT5.pro b/DFTFringe_QT5.pro index 65573323..6f1f6319 100644 --- a/DFTFringe_QT5.pro +++ b/DFTFringe_QT5.pro @@ -46,6 +46,8 @@ win32 { INCLUDEPATH += ..\qwt-6.1.6\src INCLUDEPATH += ..\build_armadillo\tmp\include INCLUDEPATH += ..\build_openCV\install\include + INCLUDEPATH += ..\build_libraw\install\include # LibRaw for camera RAW support + DEFINES += LIBRAW_NODLL # link LibRaw statically (no __declspec(dllimport)) LIBS += -L..\build_lapack\bin -llibblas LIBS += -L..\build_lapack\bin -lliblapack @@ -55,6 +57,8 @@ win32 { LIBS += -L..\build_openCV\install\x64\mingw\bin -llibopencv_highgui460 LIBS += -L..\build_openCV\install\x64\mingw\bin -llibopencv_imgcodecs460 LIBS += -L..\build_openCV\install\x64\mingw\bin -llibopencv_imgproc460 + LIBS += -L..\build_libraw\install\lib -lraw # LibRaw (static) for camera RAW support + LIBS += -lws2_32 # Winsock: LibRaw needs htons/ntohs/htonl/ntohl on Windows LIBS += -ldbghelp # for SetUnhandledExceptionFilter LIBS += -lz # zip compression library needed for cnpy.cpp @@ -221,6 +225,7 @@ SOURCES += SingleApplication/singleapplication.cpp \ psiphasedisplay.cpp \ psitiltoptions.cpp \ punwrap.cpp \ + rawimage.cpp \ regionedittools.cpp \ rejectedwavefrontsdlg.cpp \ reportdlg.cpp \ @@ -343,6 +348,7 @@ HEADERS += bezier/bezier.h \ psiphasedisplay.h \ psitiltoptions.h \ punwrap.h \ + rawimage.h \ regionedittools.h \ rejectedwavefrontsdlg.h \ reportdlg.h \ diff --git a/igramarea.cpp b/igramarea.cpp index 9f05a970..b4fbc2b6 100644 --- a/igramarea.cpp +++ b/igramarea.cpp @@ -47,6 +47,7 @@ #include #include #include +#include "rawimage.h" QVBoxLayout *debugLayout = 0; @@ -989,7 +990,17 @@ bool IgramArea::openImage(const QString &fileName, bool showBoundary) QApplication::setOverrideCursor(Qt::WaitCursor); QImage loadedImage; - if (!loadedImage.load(fileName)) { + const bool isRaw = RawImage::isRawFile(fileName); + if (isRaw){ + QString rawErr; + loadedImage = RawImage::load(fileName, &rawErr); + if (loadedImage.isNull()){ + QMessageBox::warning(NULL,"","RAW image "+fileName + " could not be read.\n" + rawErr); + QApplication::restoreOverrideCursor(); + return false; + } + } + else if (!loadedImage.load(fileName)) { QMessageBox::warning(NULL,"","Image "+fileName + " could not be read."); QApplication::restoreOverrideCursor(); return false; @@ -999,7 +1010,17 @@ bool IgramArea::openImage(const QString &fileName, bool showBoundary) loadedImage = loadedImage.convertToFormat(QImage::Format_RGB888); if (Settings2::getInstance()->m_igram->m_removeDistortion){ - cv::Mat raw = cv::imread(fileName.toStdString().c_str()); + // cv::imread cannot decode camera RAW files, so for RAW inputs build the + // BGR matrix from the already decoded QImage instead. + cv::Mat raw; + if (isRaw){ + cv::Mat rgb(loadedImage.height(), loadedImage.width(), CV_8UC3, + (void*)loadedImage.bits(), loadedImage.bytesPerLine()); + cv::cvtColor(rgb, raw, cv::COLOR_RGB2BGR); + } + else { + raw = cv::imread(fileName.toStdString().c_str()); + } QStringList parms = Settings2::getInstance()->m_igram->m_lenseParms; cv::Mat camera = cv::Mat::zeros(3,3,CV_64FC1); cv::Mat distortion =cv::Mat::zeros(1,5, CV_64FC1); diff --git a/mainwindow.cpp b/mainwindow.cpp index 863bc7bc..8e769013 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -48,6 +48,7 @@ #include "utils.h" #include "colorchannel.h" #include "opencv2/opencv.hpp" +#include "rawimage.h" #include #include @@ -298,6 +299,8 @@ void MainWindow::importIgram() { QDir dir(dirPath); QStringList filters; filters << "*.png" << "*.jpg" << "*.jpeg" << "*.bmp" << "*.gif"; // Add more image formats if needed + for (const QString &e : RawImage::extensions()) // include camera RAW files + filters << ("*." + e); dir.setNameFilters(filters); dir.setFilter(QDir::Files | QDir::Readable); @@ -486,6 +489,7 @@ void MainWindow::on_actionLoad_Interferogram_triggered() // `qDebug() << dialog.nameFilters();` // manually added upper case and first char upper case const QStringList filters({"Image files (*.bmp *.dib *.BMP *.DIB *.Bmp *.Dib *.gif *.GIF *.Gif *.jpg *.jpeg *.jpe *.JPG *.JPEG *.JPE *.Jpg *.Jpeg *.Jpe*.png *.PNG *.Png*.svg *.SVG *.Svg*.svgz *.SVGZ *.Svgz*.ico *.ICO *.Ico*.pbm *.PBM *.Pbm*.pgm *.PGM *.Pgm*.ppm *.PPM *.Ppm*.xbm *.XBM *.Xbm*.xpm *.XPM *.Xpm)", + "Camera RAW files (" + RawImage::nameFilter() + ")", "Any files (*)" }); dialog.setNameFilters(filters); diff --git a/rawimage.cpp b/rawimage.cpp new file mode 100644 index 00000000..6429c21a --- /dev/null +++ b/rawimage.cpp @@ -0,0 +1,131 @@ +/****************************************************************************** +** +** RAW camera file support for DFTFringe. See rawimage.h. +** +** This file is part of DFTFringe and is distributed under the same GNU GPLv3 +** license as the rest of the project. +** +****************************************************************************/ +#include "rawimage.h" + +#include +#include + +namespace RawImage { + +QStringList extensions() +{ + // Common camera RAW extensions handled by LibRaw. + static const QStringList exts = { + "cr2", "cr3", "crw", // Canon + "nef", "nrw", // Nikon + "arw", "srf", "sr2", // Sony + "dng", // Adobe / generic + "raf", // Fujifilm + "rw2", // Panasonic + "orf", // Olympus + "pef", "ptx", // Pentax + "raw", "rwl", // Leica + "3fr", "fff", // Hasselblad + "mef", // Mamiya + "mos", "iiq", // Leaf / Phase One + "dcr", "kdc", // Kodak + "srw", // Samsung + "erf", // Epson + "x3f" // Sigma + }; + return exts; +} + +QString nameFilter() +{ + QStringList parts; + for (const QString &e : extensions()) { + parts << ("*." + e.toLower()); + parts << ("*." + e.toUpper()); + } + return parts.join(' '); +} + +bool isRawFile(const QString &fileName) +{ + const QString suffix = QFileInfo(fileName).suffix().toLower(); + return extensions().contains(suffix); +} + +QImage load(const QString &fileName, QString *errorMsg) +{ + auto fail = [&](const QString &msg) -> QImage { + if (errorMsg) + *errorMsg = msg; + return QImage(); + }; + + LibRaw raw; + + // Processing parameters chosen to preserve fringe contrast: + // - 8 bit output so it maps straight onto the existing 8 bit pipeline + // - sRGB output colour space + // - camera white balance for a neutral looking image + // - no automatic brightness stretch, which could clip bright fringes + raw.imgdata.params.output_bps = 8; + raw.imgdata.params.output_color = 1; // sRGB + raw.imgdata.params.use_camera_wb = 1; + raw.imgdata.params.no_auto_bright = 1; + + int ret = raw.open_file(fileName.toLocal8Bit().constData()); + if (ret != LIBRAW_SUCCESS) + return fail(QStringLiteral("LibRaw open_file failed: ") + libraw_strerror(ret)); + + ret = raw.unpack(); + if (ret != LIBRAW_SUCCESS) { + raw.recycle(); + return fail(QStringLiteral("LibRaw unpack failed: ") + libraw_strerror(ret)); + } + + ret = raw.dcraw_process(); + if (ret != LIBRAW_SUCCESS) { + raw.recycle(); + return fail(QStringLiteral("LibRaw dcraw_process failed: ") + libraw_strerror(ret)); + } + + int errc = 0; + libraw_processed_image_t *proc = raw.dcraw_make_mem_image(&errc); + if (proc == nullptr || errc != LIBRAW_SUCCESS) { + if (proc) + LibRaw::dcraw_clear_mem(proc); + raw.recycle(); + return fail(QStringLiteral("LibRaw dcraw_make_mem_image failed: ") + + libraw_strerror(errc)); + } + + QImage result; + + if (proc->type == LIBRAW_IMAGE_BITMAP && proc->colors == 3 && proc->bits == 8) { + // Interleaved RGB, 8 bits per channel. QImage does not own the data, + // so copy() before proc is freed below. + QImage tmp(proc->data, proc->width, proc->height, + proc->width * 3, QImage::Format_RGB888); + result = tmp.copy(); + } + else if (proc->type == LIBRAW_IMAGE_JPEG) { + // Some cameras (e.g. Sigma X3F) hand back an embedded JPEG. + result = QImage::fromData(proc->data, proc->data_size, "JPEG"); + } + else { + LibRaw::dcraw_clear_mem(proc); + raw.recycle(); + return fail(QStringLiteral("Unsupported RAW image layout: colors=%1 bits=%2") + .arg(proc->colors).arg(proc->bits)); + } + + LibRaw::dcraw_clear_mem(proc); + raw.recycle(); + + if (result.isNull()) + return fail(QStringLiteral("Decoded RAW image was empty.")); + + return result; +} + +} // namespace RawImage diff --git a/rawimage.h b/rawimage.h new file mode 100644 index 00000000..d5b84612 --- /dev/null +++ b/rawimage.h @@ -0,0 +1,41 @@ +/****************************************************************************** +** +** RAW camera file support for DFTFringe. +** +** Reads camera RAW files (Canon CR2/CR3, Nikon NEF, Sony ARW, Adobe DNG, +** Panasonic RW2, etc.) using LibRaw and returns a demosaiced QImage that the +** rest of DFTFringe can treat exactly like a jpg/png/bmp interferogram. +** +** This file is part of DFTFringe and is distributed under the same GNU GPLv3 +** license as the rest of the project. +** +****************************************************************************/ +#ifndef RAWIMAGE_H +#define RAWIMAGE_H + +#include +#include +#include + +namespace RawImage { + +// Returns the list of supported RAW extensions, lower case, without the +// leading dot, e.g. {"cr2", "nef", "arw", "dng", ...}. +QStringList extensions(); + +// Returns a QFileDialog name-filter fragment containing every supported RAW +// extension in both lower and upper case, e.g. +// "*.cr2 *.CR2 *.nef *.NEF ..." +QString nameFilter(); + +// True if fileName has one of the RAW extensions above (case insensitive). +bool isRawFile(const QString &fileName); + +// Decodes a camera RAW file into an 8 bit RGB QImage (Format_RGB888). +// Returns a null QImage on failure; if errorMsg is non-null it receives a +// human readable description of the failure. +QImage load(const QString &fileName, QString *errorMsg = nullptr); + +} // namespace RawImage + +#endif // RAWIMAGE_H diff --git a/zernikeeditdlg.cpp b/zernikeeditdlg.cpp index f7815d2d..8a4972e3 100644 --- a/zernikeeditdlg.cpp +++ b/zernikeeditdlg.cpp @@ -149,11 +149,24 @@ void zernikeEditDlg::on_useCurrent_clicked() QMessageBox::warning(0,"No wave fronts available!", "First load or create a wave front"); return; } - m_zernEnables = zernEnables; - tableModel->blockSignals(true); + wavefront *wf = m_sm->m_wavefronts[m_sm->m_currentNdx]; + + // Re-fit the current wavefront to the max order selected in this dialog. + // wf->InputZerns is fitted with zernikePolar which is capped at Z_TERMS (49, + // i.e. order 12); ZernFitWavefront uses zpmC and honours m_maxOrder, so at + // order 22 it returns all (maxOrder/2+1)^2 = 144 coefficients in the same + // sequential order as the .zrn file. This is what makes a later Save write + // the actually-selected number of terms. + zernikeProcess &zp = *zernikeProcess::get_Instance(); + zp.setMaxOrder(m_maxOrder); + std::vector zs = zp.ZernFitWavefront(*wf); + if (zs.empty()) // fit cancelled by the user + return; - tableModel->setValues(m_sm->m_wavefronts[m_sm->m_currentNdx]->InputZerns,m_sm->m_wavefronts[m_sm->m_currentNdx]->useSANull ); - ui->sizeSb->setValue(m_sm->m_wavefronts[m_sm->m_currentNdx]->data.cols); + m_zernEnables.assign(zs.size(), true); + tableModel->blockSignals(true); + tableModel->setValues(zs, wf->useSANull); + ui->sizeSb->setValue(wf->data.cols); tableModel->blockSignals(false); tableModel->update(); } @@ -174,6 +187,7 @@ void zernikeEditDlg::on_maxOrder_valueChanged(int arg1) m_noOfTerms = zp.getNumberOfTerms(); ui->numberOfTerms->setText(QString("%1 Terms").arg(m_noOfTerms)); tableModel->resizeRows(m_noOfTerms); + m_zernEnables.resize(m_noOfTerms, true); // keep enables in sync with the term count emit termCountChanged(m_noOfTerms); }