Skip to content

Commit 4ab777f

Browse files
Add support of Dropbox's Lepton
1 parent efd55a1 commit 4ab777f

19 files changed

+383
-93
lines changed

extras/scripts/JPEGView.vcxproj-postbuild-event.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ call :COPY_DLLS libheif
6666
call :COPY_DLLS libavif
6767
call :COPY_DLLS lcms2
6868
call :COPY_DLLS libraw
69-
69+
call :COPY_DLLS lepton_jpeg
7070

7171
exit /b 0
7272

src/JPEGView/FileExtensionsDlg.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "HelpersGUI.h"
55
#include "SettingsProvider.h"
66
#include "FileExtensionsRegistry.h"
7+
#include "lepton_wrapper.h"
78

89
///////////////////////////////////////////////////////////////////////////////////
910
// Helpers
@@ -294,18 +295,19 @@ void CFileExtensionsDlg::FillFileExtensionsList() {
294295
InsertExtension(_T("*.psd"), FormatHint(CNLS::GetString(_T("%s images")), _T("Photoshop Document")));
295296
InsertExtensions(CSettingsProvider::This().FilesProcessedByWIC(), CNLS::GetString(_T("%s images (processed by Window Imaging Component - WIC)")));
296297
InsertExtensions(CSettingsProvider::This().FileEndingsRAW(), CNLS::GetString(_T("%s camera raw images (embedded JPEGs only)")));
298+
if (lepton_wrapper::LeptonSupport())
299+
InsertExtensions(CSettingsProvider::This().FilesProcessedByLepton(), CNLS::GetString(_T("%s images")), _T("Dropbox's Lepton"));
297300
}
298301

299-
void CFileExtensionsDlg::InsertExtensions(LPCTSTR sExtensionList, LPCTSTR sHint) {
302+
void CFileExtensionsDlg::InsertExtensions(LPCTSTR sExtensionList, LPCTSTR sHint, LPCTSTR param) {
300303
int nNumChars = (int)_tcslen(sExtensionList);
301304
int nStart = 0;
302305
for (int i = 0; i <= nNumChars; i++) {
303306
if (sExtensionList[i] == _T(';') || sExtensionList[i] == 0) {
304307
CString sExtension(&sExtensionList[nStart], i - nStart);
305308
if (sExtension.GetLength() >= 3) {
306-
CString sExtensionUpper(&((LPCTSTR)sExtension)[2]);
307-
sExtensionUpper.MakeUpper();
308-
InsertExtension(sExtension, FormatHint(sHint, sExtensionUpper));
309+
CString sExtensionUpper(param ? param : &((LPCTSTR)sExtension)[2]);
310+
InsertExtension(sExtension, FormatHint(sHint, param ? sExtensionUpper : sExtensionUpper.MakeUpper()));
309311
}
310312
nStart = i + 1;
311313
}

src/JPEGView/FileExtensionsDlg.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ class CFileExtensionsDlg : public CDialogImpl<CFileExtensionsDlg>
4444

4545
void FillFileExtensionsList();
4646
void InsertExtension(LPCTSTR sExtension, LPCTSTR sHint);
47-
void InsertExtensions(LPCTSTR sExtensionList, LPCTSTR sHint);
47+
void InsertExtensions(LPCTSTR sExtensionList, LPCTSTR sHint, LPCTSTR param = NULL);
4848
};

src/JPEGView/FileList.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Helpers.h"
55
#include "DirectoryWatcher.h"
66
#include "Shlwapi.h"
7+
#include "lepton_wrapper.h"
78

89
///////////////////////////////////////////////////////////////////////////////////
910
// Helpers
@@ -182,6 +183,11 @@ static LPCTSTR* GetSupportedFileEndingList() {
182183
if (_tcslen(sFileEndingsWIC) > 2 && WICPresentGuarded()) {
183184
ParseAndAddFileEndings(sFileEndingsWIC);
184185
}
186+
187+
if (lepton_wrapper::LeptonSupport()) {
188+
ParseAndAddFileEndings(CSettingsProvider::This().FilesProcessedByLepton());
189+
}
190+
185191
ParseAndAddFileEndings(CSettingsProvider::This().FileEndingsRAW());
186192
}
187193
return sFileEndings;

src/JPEGView/Helpers.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,8 @@ EImageFormat GetImageFormat(LPCTSTR sFileName) {
779779
return IF_WIC;
780780
} else if (IsInFileEndingList(CSettingsProvider::This().FileEndingsRAW(), sEnding)) {
781781
return IF_CameraRAW;
782+
} else if (IsInFileEndingList(CSettingsProvider::This().FilesProcessedByLepton(), sEnding)) {
783+
return IF_Lepton;
782784
}
783785
}
784786
return IF_Unknown;
@@ -971,4 +973,10 @@ int GetWindowsVersion() {
971973
return osvi.dwMajorVersion * 100 + osvi.dwMinorVersion;
972974
}
973975

976+
CString GetTempPath() {
977+
TCHAR tempPath[MAX_PATH];
978+
tempPath[0] = 0;
979+
::GetTempPath(MAX_PATH, tempPath);
980+
return tempPath;
981+
}
974982
}

src/JPEGView/Helpers.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@ namespace Helpers {
275275
// Returns the windows version in the format Major * 100 + Minor, e.g. 602 for Windows 8
276276
int GetWindowsVersion();
277277

278+
// Returns the system's temporary directory.
279+
CString GetTempPath();
280+
278281
// Conversion class that replaces the | by null character in a string.
279282
// Caution: Uses a static buffer and therefore only one string can be replaced concurrently
280283
const int MAX_SIZE_REPLACE_PIPE = 1024;

src/JPEGView/ImageLoadThread.cpp

Lines changed: 113 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
#include "StdAfx.h"
32
#include "ImageLoadThread.h"
43
#include <gdiplus.h>
@@ -22,7 +21,7 @@
2221
#include "QOIWrapper.h"
2322
#include "PSDWrapper.h"
2423
#include "MaxImageDef.h"
25-
24+
#include "lepton_wrapper.h"
2625

2726
using namespace Gdiplus;
2827

@@ -62,6 +61,8 @@ static EImageFormat GetImageFormat(LPCTSTR sFileName) {
6261
} else if ((header[0] == 0xff && header[1] == 0x0a) ||
6362
memcmp(header, "\x00\x00\x00\x0cJXL\x20\x0d\x0a\x87\x0a", 12) == 0) {
6463
return IF_JXL;
64+
} else if (header[0] == 0xcf && header[1] == 0x84) {
65+
return IF_Lepton;
6566
} else if (!memcmp(header+4, "ftyp", 4)) {
6667
// https://github.com/strukturag/libheif/issues/83
6768
// https://github.com/strukturag/libheif/blob/ce1e4586b6222588c5afcd60c7ba9caa86bcc58c/libheif/heif.h#L602-L805
@@ -117,7 +118,7 @@ static EImageFormat GetBitmapFormat(Gdiplus::Bitmap * pBitmap) {
117118
}
118119
}
119120

120-
static CJPEGImage* ConvertGDIPlusBitmapToJPEGImage(Gdiplus::Bitmap* pBitmap, int nFrameIndex, void* pEXIFData,
121+
static CJPEGImage* ConvertGDIPlusBitmapToJPEGImage(Gdiplus::Bitmap* pBitmap, int nFrameIndex, void* pEXIFData,
121122
__int64 nJPEGHash, bool &isOutOfMemory, bool &isAnimatedGIF) {
122123

123124
isOutOfMemory = false;
@@ -289,7 +290,7 @@ void CImageLoadThread::ProcessRequest(CRequestBase& request) {
289290
}
290291

291292
CRequest& rq = (CRequest&)request;
292-
double dStartTime = Helpers::GetExactTickCount();
293+
double dStartTime = Helpers::GetExactTickCount();
293294
// Get image format and read the image
294295
switch (GetImageFormat(rq.FileName)) {
295296
case IF_JPEG :
@@ -370,6 +371,10 @@ void CImageLoadThread::ProcessRequest(CRequestBase& request) {
370371
ProcessReadRAWRequest(&rq);
371372
break;
372373
#endif
374+
case IF_Lepton:
375+
DeleteCachedGDIBitmap();
376+
ProcessReadLeptonRequest(&rq);
377+
break;
373378
case IF_QOI:
374379
DeleteCachedGDIBitmap();
375380
DeleteCachedWebpDecoder();
@@ -397,7 +402,7 @@ void CImageLoadThread::ProcessRequest(CRequestBase& request) {
397402
}
398403
// then process the image if read was successful
399404
if (rq.Image != NULL) {
400-
rq.Image->SetLoadTickCount(Helpers::GetExactTickCount() - dStartTime);
405+
rq.Image->SetLoadTickCount(Helpers::GetExactTickCount() - dStartTime);
401406
if (!ProcessImageAfterLoad(&rq)) {
402407
delete rq.Image;
403408
rq.Image = NULL;
@@ -471,88 +476,78 @@ void CImageLoadThread::DeleteCachedAvifDecoder() {
471476
#endif
472477
}
473478

474-
void CImageLoadThread::ProcessReadJPEGRequest(CRequest * request) {
475-
HANDLE hFile = ::CreateFile(request->FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
476-
if (hFile == INVALID_HANDLE_VALUE) {
477-
return;
478-
}
479-
480-
HGLOBAL hFileBuffer = NULL;
481-
void* pBuffer = NULL;
479+
void CImageLoadThread::ProcessReadJPEGRequest(CRequest* request, const uint8_t* buffer, UINT size) {
482480
try {
483-
// Don't read too huge files
484-
long long nFileSize = Helpers::GetFileSize(hFile);
485-
if (nFileSize > MAX_JPEG_FILE_SIZE) {
486-
request->OutOfMemory = true;
487-
::CloseHandle(hFile);
488-
return;
489-
}
490-
hFileBuffer = ::GlobalAlloc(GMEM_MOVEABLE, nFileSize);
491-
pBuffer = (hFileBuffer == NULL) ? NULL : ::GlobalLock(hFileBuffer);
492-
if (pBuffer == NULL) {
493-
if (hFileBuffer) ::GlobalFree(hFileBuffer);
494-
request->OutOfMemory = true;
495-
::CloseHandle(hFile);
496-
return;
497-
}
498-
unsigned int nNumBytesRead;
499-
if (::ReadFile(hFile, pBuffer, nFileSize, (LPDWORD) &nNumBytesRead, NULL) && nNumBytesRead == nFileSize) {
500-
bool bUseGDIPlus = CSettingsProvider::This().ForceGDIPlus() || CSettingsProvider::This().UseEmbeddedColorProfiles();
501-
if (bUseGDIPlus) {
502-
IStream* pStream = NULL;
503-
if (::CreateStreamOnHGlobal(hFileBuffer, FALSE, &pStream) == S_OK) {
504-
Gdiplus::Bitmap* pBitmap = Gdiplus::Bitmap::FromStream(pStream, CSettingsProvider::This().UseEmbeddedColorProfiles());
505-
bool isOutOfMemory, isAnimatedGIF;
506-
request->Image = ConvertGDIPlusBitmapToJPEGImage(pBitmap, 0, Helpers::FindEXIFBlock(pBuffer, nFileSize),
507-
Helpers::CalculateJPEGFileHash(pBuffer, nFileSize), isOutOfMemory, isAnimatedGIF);
508-
request->OutOfMemory = request->Image == NULL && isOutOfMemory;
509-
if (request->Image != NULL) {
510-
request->Image->SetJPEGComment(Helpers::GetJPEGComment(pBuffer, nFileSize));
511-
}
512-
pStream->Release();
513-
delete pBitmap;
514-
} else {
515-
request->OutOfMemory = true;
481+
bool bUseGDIPlus = CSettingsProvider::This().ForceGDIPlus() || CSettingsProvider::This().UseEmbeddedColorProfiles();
482+
if (bUseGDIPlus) {
483+
CComPtr<IStream> pStream(::SHCreateMemStream(buffer, size));
484+
if (pStream) {
485+
std::shared_ptr<Gdiplus::Bitmap> pBitmap(Gdiplus::Bitmap::FromStream(pStream, CSettingsProvider::This().UseEmbeddedColorProfiles()));
486+
bool isOutOfMemory, isAnimatedGIF;
487+
request->Image = ConvertGDIPlusBitmapToJPEGImage(pBitmap.get(), 0, Helpers::FindEXIFBlock((void*)buffer, size),
488+
Helpers::CalculateJPEGFileHash((void*)buffer, size), isOutOfMemory, isAnimatedGIF);
489+
request->OutOfMemory = request->Image == NULL && isOutOfMemory;
490+
if (request->Image != NULL) {
491+
request->Image->SetJPEGComment(Helpers::GetJPEGComment((void*)buffer, size));
516492
}
517493
}
518-
if (!bUseGDIPlus || request->OutOfMemory) {
519-
int nWidth, nHeight, nBPP;
520-
TJSAMP eChromoSubSampling;
521-
bool bOutOfMemory;
522-
// int nTicks = ::GetTickCount();
523-
524-
void* pPixelData = TurboJpeg::ReadImage(nWidth, nHeight, nBPP, eChromoSubSampling, bOutOfMemory, pBuffer, nFileSize);
525-
526-
/*
527-
TCHAR buffer[20];
528-
_stprintf_s(buffer, 20, _T("%d"), ::GetTickCount() - nTicks);
529-
::MessageBox(NULL, CString(_T("Elapsed ticks: ")) + buffer, _T("Time"), MB_OK);
530-
*/
531-
532-
// Color and b/w JPEG is supported
533-
if (pPixelData != NULL && (nBPP == 3 || nBPP == 1)) {
534-
request->Image = new CJPEGImage(nWidth, nHeight, pPixelData,
535-
Helpers::FindEXIFBlock(pBuffer, nFileSize), nBPP,
536-
Helpers::CalculateJPEGFileHash(pBuffer, nFileSize), IF_JPEG, false, 0, 1, 0);
537-
request->Image->SetJPEGComment(Helpers::GetJPEGComment(pBuffer, nFileSize));
538-
request->Image->SetJPEGChromoSampling(eChromoSubSampling);
539-
} else if (bOutOfMemory) {
540-
request->OutOfMemory = true;
541-
} else {
542-
// failed, try GDI+
543-
delete[] pPixelData;
544-
ProcessReadGDIPlusRequest(request);
545-
}
494+
else {
495+
request->OutOfMemory = true;
546496
}
547497
}
548-
} catch (...) {
549-
delete request->Image;
498+
if (!bUseGDIPlus || request->OutOfMemory) {
499+
int nWidth, nHeight, nBPP;
500+
TJSAMP eChromoSubSampling;
501+
bool bOutOfMemory;
502+
503+
void* pPixelData = TurboJpeg::ReadImage(nWidth, nHeight, nBPP, eChromoSubSampling, bOutOfMemory, (const void*)buffer, size);
504+
505+
// Color and b/w JPEG is supported
506+
if (pPixelData != NULL && (nBPP == 3 || nBPP == 1)) {
507+
request->Image = new CJPEGImage(nWidth, nHeight, pPixelData,
508+
Helpers::FindEXIFBlock((void*)buffer, size), nBPP,
509+
Helpers::CalculateJPEGFileHash((void*)buffer, size), IF_JPEG, false, 0, 1, 0);
510+
request->Image->SetJPEGComment(Helpers::GetJPEGComment((void*)buffer, size));
511+
request->Image->SetJPEGChromoSampling(eChromoSubSampling);
512+
}
513+
else if (bOutOfMemory) {
514+
request->OutOfMemory = true;
515+
}
516+
else {
517+
// failed, try GDI+
518+
delete[] pPixelData;
519+
ProcessReadGDIPlusRequest(request);
520+
}
521+
}
522+
}
523+
catch (...) {
550524
request->Image = NULL;
551525
request->ExceptionError = true;
552526
}
553-
::CloseHandle(hFile);
554-
if (pBuffer) ::GlobalUnlock(hFileBuffer);
555-
if (hFileBuffer) ::GlobalFree(hFileBuffer);
527+
}
528+
529+
void CImageLoadThread::ProcessReadJPEGRequest(CRequest* request) {
530+
ATL::CAtlFile file;
531+
if (FAILED(file.Create(request->FileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING))) {
532+
return;
533+
}
534+
535+
ULONGLONG size = 0;
536+
if (FAILED(file.GetSize(size))) {
537+
return;
538+
}
539+
540+
if (size > MAX_JPEG_FILE_SIZE) {
541+
request->OutOfMemory = true;
542+
return;
543+
}
544+
545+
CAtlFileMapping<uint8_t> file_map;
546+
if (FAILED(file_map.MapFile(file, 0, 0, PAGE_READONLY, FILE_MAP_READ))) {
547+
return;
548+
}
549+
550+
ProcessReadJPEGRequest(request, file_map, (UINT)size);
556551
}
557552

558553

@@ -715,7 +710,7 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) {
715710
request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_PNG, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs);
716711
} else {
717712
DeleteCachedPngDecoder();
718-
713+
719714
IStream* pStream = NULL;
720715
if (::CreateStreamOnHGlobal(hFileBuffer, FALSE, &pStream) == S_OK) {
721716
Gdiplus::Bitmap* pBitmap = Gdiplus::Bitmap::FromStream(pStream, CSettingsProvider::This().UseEmbeddedColorProfiles());
@@ -861,7 +856,7 @@ void CImageLoadThread::ProcessReadAVIFRequest(CRequest* request) {
861856
int nWidth, nHeight, nBPP, nFrameCount, nFrameTimeMs;
862857
bool bHasAnimation;
863858
void* pEXIFData;
864-
uint8* pPixelData = (uint8*)AvifReader::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, request->FrameIndex,
859+
uint8* pPixelData = (uint8*)AvifReader::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, request->FrameIndex,
865860
nFrameCount, nFrameTimeMs, pEXIFData, request->OutOfMemory, pBuffer, nFileSize);
866861
if (pPixelData != NULL) {
867862
if (bHasAnimation)
@@ -1062,6 +1057,41 @@ void CImageLoadThread::ProcessReadGDIPlusRequest(CRequest * request) {
10621057
}
10631058
}
10641059

1060+
void CImageLoadThread::ProcessReadLeptonRequest(CRequest* request) {
1061+
ATL::CAtlFile file;
1062+
if (FAILED(file.Create(request->FileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING))) {
1063+
return;
1064+
}
1065+
1066+
ULONGLONG size = 0;
1067+
if (FAILED(file.GetSize(size)))
1068+
{
1069+
return;
1070+
}
1071+
1072+
CAtlFileMapping<uint8_t> file_map;
1073+
if (FAILED(file_map.MapFile(file, 0, 0, PAGE_READONLY, FILE_MAP_READ)))
1074+
{
1075+
return;
1076+
}
1077+
1078+
// Get the lepton library.
1079+
auto& lib = lepton_wrapper::lib_lepton::get();
1080+
1081+
// As we don't know how big the buffer should be for storing a resulting JPEG image,
1082+
// allocating it twice bigger than the input LEPTON file size.
1083+
// It should be enough for most cases.
1084+
auto buffer_size = size * 2;
1085+
1086+
// Extract a JPEG data from the lepton file and pass it to JPEG handler for further decoding.
1087+
std::vector<uint8_t> buffer(buffer_size);
1088+
uint8_t* pBuffer = &buffer[0];
1089+
uint64_t jpeg_size = {};
1090+
if (0 == lib.WrapperDecompressImage(file_map, size, pBuffer, buffer_size, 1, &jpeg_size) && jpeg_size > 0) {
1091+
ProcessReadJPEGRequest(request, pBuffer, (UINT)jpeg_size);
1092+
}
1093+
}
1094+
10651095
static unsigned char* alloc(int sizeInBytes) {
10661096
return new(std::nothrow) unsigned char[sizeInBytes];
10671097
}
@@ -1111,7 +1141,7 @@ bool CImageLoadThread::ProcessImageAfterLoad(CRequest * request) {
11111141
double dZoom = request->ProcessParams.Zoom;
11121142
CSize newSize;
11131143
if (dZoom < 0.0) {
1114-
newSize = Helpers::GetImageRect(nWidth, nHeight,
1144+
newSize = Helpers::GetImageRect(nWidth, nHeight,
11151145
request->ProcessParams.TargetWidth, request->ProcessParams.TargetHeight, request->ProcessParams.AutoZoomMode, dZoom);
11161146
} else {
11171147
newSize = CSize((int)(nWidth*dZoom + 0.5), (int)(nHeight*dZoom + 0.5));
@@ -1121,7 +1151,7 @@ bool CImageLoadThread::ProcessImageAfterLoad(CRequest * request) {
11211151
newSize.cy = max(1, min(65535, newSize.cy)); // max size must not be bigger than this after zoom
11221152

11231153
// clip to target rectangle
1124-
CSize clippedSize(min(request->ProcessParams.TargetWidth, newSize.cx),
1154+
CSize clippedSize(min(request->ProcessParams.TargetWidth, newSize.cx),
11251155
min(request->ProcessParams.TargetHeight, newSize.cy));
11261156

11271157
LimitOffsets(request->ProcessParams.Offsets, CSize(request->ProcessParams.TargetWidth, request->ProcessParams.TargetHeight), newSize);

0 commit comments

Comments
 (0)