Skip to content

Commit daa5d9a

Browse files
Egor VakhromtsevEgor Vakhromtsev
Egor Vakhromtsev
authored and
Egor Vakhromtsev
committed
More optimizations and fixes
1 parent 9702f5b commit daa5d9a

21 files changed

+349
-71
lines changed

README.md

+4-8
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
### Benefits
88

99
* Can read and composite RAW files with libraw. Useful for making previews of your shots before you start processing RAWs in your favourite processing software.
10-
* Reads many formats (with ImageMagick).
11-
* Can composite images with different methods (which ImageMagick support).
10+
* Reads many formats (with GraphicsMagick).
11+
* Can composite images with different methods (which GraphicsMagick support).
1212
* Ability to take fast previews of star trails or time lapse.
1313
* Cross platform. Should works on Linux, Mac and Windows.
1414

@@ -66,11 +66,7 @@ So as you can clue, at one moment I finally saying that enough for me.
6666
And start make StarTrailer - the software which can gives you fast and nice preview of your great startrails.
6767
And you can just play with your night shots, even they stored in complicated RAW format.
6868

69-
At the end of this story I want to send very big thanks to great authors of cool libs without which StarTrails can not were builded:
70-
71-
* ImageMagick - http://www.imagemagick.org/
72-
* Libraw - http://www.libraw.su/
73-
* Qt - http://qt-project.org/
69+
At the end of this story I want to send very big thanks to great authors of cool libs without which StarTrails can not were builded.
7470

7571
Thanks a lot!
7672

@@ -83,7 +79,7 @@ Copyright (c) 2013-2024 Egor Vakhromtsev MIT Licensed, see [LICENSE-MIT] for det
8379

8480
#### How to make star trails without this software
8581

86-
1. Write your own script that use `convert -composite` from ImageMagick. Like [this][3] or [this one][5].
82+
1. Write your own script that use `convert -composite` from ImageMagick/GraphicsMagick. Like [this][3] or [this one][5].
8783
2. If you are on windows try [Startrails][6] application.
8884

8985

StarTrailer.pro

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
TEMPLATE = subdirs
22
CONFIG += debug_and_release
3+
CONFIG += c++17
34
SUBDIRS = src tests
45
QMAKE_CXXFLAGS += -Wall -Wno-error
6+

src/compositetrailstask.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ CompositeTrailsTask::~CompositeTrailsTask()
1515

1616
void CompositeTrailsTask::run()
1717
{
18-
qDebug() << "CompositeTrailsTask::run() starts";
18+
// qDebug() << "CompositeTrailsTask::run() starts";
1919
if (*m_stopped)
2020
return;
2121

@@ -37,7 +37,7 @@ void CompositeTrailsTask::run()
3737

3838
std::string s = m_sourceFiles[i].toStdString();
3939
try {
40-
image.read(s, m_raw_processing_mode);
40+
image.read(s, m_raw_processing_mode, m_jpeg_processing_mode);
4141
} catch (std::runtime_error &e) {
4242
qDebug() << "Can't read file: " << m_sourceFiles[i] << "\n" << e.what();
4343
continue;
@@ -87,8 +87,8 @@ void CompositeTrailsTask::run()
8787
if (counter > 0)
8888
{
8989
m_mutex->lock();
90-
qDebug() << "preview WxH = " << m_preview_image->width() << "x" << m_preview_image->height();
91-
qDebug() << "Tmp WxH = " << m_tmp_image->width() << "x" << m_tmp_image->height();
90+
// qDebug() << "preview WxH = " << m_preview_image->width() << "x" << m_preview_image->height();
91+
// qDebug() << "Tmp WxH = " << m_tmp_image->width() << "x" << m_tmp_image->height();
9292

9393
// if (m_out_image->height() == 0 && m_out_image->width() == 0) {
9494
// //delete m_out_image;
@@ -108,12 +108,12 @@ void CompositeTrailsTask::run()
108108
delete resized_img;
109109
}
110110

111-
qDebug() << "Tmp WxH = " << m_tmp_image->width() << "x" << m_tmp_image->height();
111+
// qDebug() << "Tmp WxH = " << m_tmp_image->width() << "x" << m_tmp_image->height();
112112

113113
QMetaObject::invokeMethod(m_receiver, "redrawPreview", Qt::QueuedConnection, Q_ARG(bool, true));
114114
m_mutex->unlock();
115115
}
116116

117117
QMetaObject::invokeMethod(m_receiver, "composingFinished", Qt::QueuedConnection);
118-
qDebug() << "CompositeTrailsTask::run() ended";
118+
// qDebug() << "CompositeTrailsTask::run() ended";
119119
}

src/compositetrailstask.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class CompositeTrailsTask : public QRunnable
2121
// StarTrailer::Image *out,
2222
StarTrailer::Image *preview,
2323
const Magick::CompositeOperator compose_op=Magick::LightenCompositeOp,
24-
const StarTrailer::Image::RawProcessingMode raw_mode=StarTrailer::Image::FullPreview)
24+
const StarTrailer::Image::RawProcessingMode raw_mode=StarTrailer::Image::FullPreview,
25+
const StarTrailer::Image::JPEGProcessingMode jpeg_mode=StarTrailer::Image::FullJpeg)
2526

2627
: m_receiver(receiver),
2728
m_stopped(stopped),
@@ -33,7 +34,8 @@ class CompositeTrailsTask : public QRunnable
3334
m_preview_image(preview),
3435
// m_out_image(out),
3536
m_compose_op(compose_op),
36-
m_raw_processing_mode(raw_mode)
37+
m_raw_processing_mode(raw_mode),
38+
m_jpeg_processing_mode(jpeg_mode)
3739
{
3840
//m_out_image(new Magick::Image(files.first().toStdString()))
3941
*m_stopped=false;
@@ -61,6 +63,7 @@ class CompositeTrailsTask : public QRunnable
6163
StarTrailer::Image * m_tmp_image = 0;
6264
const Magick::CompositeOperator m_compose_op;
6365
StarTrailer::Image::RawProcessingMode m_raw_processing_mode = StarTrailer::Image::FullPreview;
66+
StarTrailer::Image::JPEGProcessingMode m_jpeg_processing_mode = StarTrailer::Image::FullJpeg;
6467
};
6568

6669
#endif // COMPOSITETRAILSTASK_H

src/iconproxy.h

+28-12
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
#include <QIdentityProxyModel>
55
#include <QFileSystemModel>
6-
#include <QtWidgets>
76
#include <QtConcurrent/QtConcurrent>
87
#include <QThreadPool>
8+
#include <QCache>
99
#include <QDebug>
1010

1111
/// A thread-safe function that returns an icon for an item with a given path.
@@ -14,12 +14,15 @@ QIcon getThumbnailIcon(const QString & path);
1414

1515
class IconProxy : public QIdentityProxyModel {
1616
Q_OBJECT
17-
QMap<QString, QIcon> m_icons;
17+
// QMap<QString, QIcon> m_icons;
18+
QCache<QString, const QIcon> m_icons;
1819
Q_SIGNAL void hasIcon(const QString&, const QIcon&, const QPersistentModelIndex& index) const;
1920

2021
void onIcon(const QString& path, const QIcon& icon, const QPersistentModelIndex& index) {
21-
m_icons.insert(path, icon);
22-
emit dataChanged(index, index, QVector<int>{QFileSystemModel::FileIconRole});
22+
if (!icon.isNull()) {
23+
m_icons.insert(path, new QIcon(icon));
24+
emit dataChanged(index, index, QVector<int>{QFileSystemModel::FileIconRole});
25+
}
2326
}
2427
QThreadPool *runnersPool=0;
2528

@@ -29,21 +32,34 @@ class IconProxy : public QIdentityProxyModel {
2932

3033
if (role == QFileSystemModel::FileIconRole && index.column() == 0) {
3134
auto path = index.data(QFileSystemModel::FilePathRole).toString();
32-
auto it = m_icons.find(path);
33-
if (it != m_icons.end()) {
35+
const QIcon *it = m_icons[path];
36+
if (it != nullptr) {
37+
return *it;
38+
} else {
39+
QPersistentModelIndex pIndex{index};
40+
QtConcurrent::run(runnersPool, [this,path,pIndex]{
41+
emit hasIcon(path, getThumbnailIcon(path), pIndex);
42+
});
43+
}
44+
45+
/*f (it != m_icons.end()) {
3446
if (! it->isNull()) return *it;
3547
return QIdentityProxyModel::data(index, role);
36-
}
37-
QPersistentModelIndex pIndex{index};
38-
QtConcurrent::run(runnersPool, [this,path,pIndex]{
39-
emit hasIcon(path, getThumbnailIcon(path), pIndex);
40-
});
41-
return QVariant{};
48+
}*/
49+
50+
// QPersistentModelIndex pIndex{index};
51+
// QtConcurrent::run(runnersPool, [this,path,pIndex]{
52+
// emit hasIcon(path, getThumbnailIcon(path), pIndex);
53+
// });
54+
// return QIcon();
55+
// return QVariant{};
56+
return QIdentityProxyModel::data(index, role);
4257
}
4358
return QIdentityProxyModel::data(index, role);
4459
}
4560

4661
IconProxy(QObject * parent = nullptr) : QIdentityProxyModel{parent} {
62+
m_icons.setMaxCost(8192);
4763
connect(this, &IconProxy::hasIcon, this, &IconProxy::onIcon);
4864

4965
runnersPool = new QThreadPool();

src/image.cpp

+96-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
#include "image.h"
22
#include <cassert>
3+
#include <cstddef>
4+
#include <fcntl.h>
5+
#include <unistd.h>
6+
#include <stdio.h>
7+
#include <stdlib.h>
8+
#include <sys/stat.h>
9+
#include <filesystem> // C++17
10+
#include <algorithm>
11+
#include <string>
312

413
const int DEFAULT_JPEG_QUALITY=95;
514

@@ -16,10 +25,10 @@ Image::Image(const Image &from_image)
1625
raw_processor = new LibRaw();
1726
}
1827

19-
Image::Image(const std::string &file, Image::RawProcessingMode raw_processing_mode)
28+
Image::Image(const std::string &file, Image::RawProcessingMode raw_processing_mode, Image::JPEGProcessingMode jpeg_processing_mode)
2029
{
2130
init();
22-
read(file, raw_processing_mode);
31+
read(file, raw_processing_mode, jpeg_processing_mode);
2332
}
2433

2534
Image::Image(const Magick::Image &from_image)
@@ -43,13 +52,24 @@ Image::~Image()
4352
// }
4453
}
4554

46-
void Image::read(const std::string &file, RawProcessingMode raw_processing_mode) {
55+
void Image::read(const std::string &file, RawProcessingMode raw_processing_mode, JPEGProcessingMode jpeg_processing_mode) {
4756
const std::lock_guard<std::mutex> lock(image_mutex);
57+
std::string extension = file.substr(file.find_last_of(".") + 1);
58+
std::transform(extension.begin(), extension.end(), extension.begin(), ::toupper);
59+
60+
if (jpeg_processing_mode == PreviewJPEG && (extension == "JPG" || extension == "JPEG" || extension == "JPE")) {
61+
try {
62+
read_preview_from_jpeg(file);
63+
return;
64+
} catch (const std::runtime_error &e) {
65+
// do nothing
66+
}
67+
}
4868

4969
assert(raw_processor != 0);
5070
if (LIBRAW_SUCCESS == raw_processor->open_file(file.c_str())) {
5171
try {
52-
switch (raw_processing_mode) {
72+
switch (raw_processing_mode) {
5373
case FullPreview:
5474
read_preview_with_libraw(file);
5575
break;
@@ -126,7 +146,7 @@ void Image::read_with_image_magick(const std::string &file)
126146
}
127147
catch(Magick::Error &error)
128148
{
129-
throw std::runtime_error(std::string("Can't open file: ") + file + std::string("ImageMagick reports: ") + error.what());
149+
throw std::runtime_error(std::string("Can't open file: ") + file + std::string("GraphicsMagick reports: ") + error.what());
130150
}
131151
catch( std::exception &error )
132152
{
@@ -151,6 +171,11 @@ void Image::read_preview_with_libraw(const std::string &file)
151171
{
152172
// memory copied here
153173
Magick::Blob blob(raw_processor->imgdata.thumbnail.thumb, raw_processor->imgdata.thumbnail.tlength);
174+
175+
// How no avoid double free (via blob destructor and libraw's resycle method?)
176+
// Magick::Blob blob;//(buf, preview_length);
177+
// blob.updateNoCopy(raw_processor->imgdata.thumbnail.thumb, raw_processor->imgdata.thumbnail.tlength, Magick::Blob::MallocAllocator);
178+
154179
image->read(blob);
155180
}
156181
break;
@@ -176,6 +201,72 @@ void Image::read_preview_with_libraw(const std::string &file)
176201
raw_processor->recycle();
177202
}
178203

204+
void Image::read_preview_from_jpeg(const std::string &file)
205+
{
206+
assert(raw_processor != 0);
207+
struct stat statbuf;
208+
209+
if (stat(file.c_str(), &statbuf) == -1)
210+
{
211+
throw std::runtime_error("failed to stat!");
212+
}
213+
214+
FILE *f = fopen(file.c_str(), "rb");
215+
if (f == NULL) {
216+
throw std::runtime_error("failed open file!");
217+
}
218+
219+
int c=-1;
220+
int prev=-1;
221+
bool in_image = 0;
222+
size_t from=0;
223+
size_t to=0;
224+
225+
const int max_preview_length = 1024*1024;
226+
fseek(f, statbuf.st_size - max_preview_length, SEEK_SET);
227+
228+
while ((c = fgetc(f)) != EOF){
229+
if (prev == 0xFF) {
230+
if (in_image == 0 && c == 0xD8) {
231+
in_image = 1;
232+
from = ftell(f)-2;
233+
} else {
234+
if (in_image && c == 0xD9) {
235+
to = ftell(f);
236+
in_image = 0;
237+
}
238+
}
239+
}
240+
241+
prev = c;
242+
};
243+
244+
size_t preview_length = to - from;
245+
if (preview_length == 0) {
246+
fclose(f);
247+
throw std::runtime_error("jpeg preview not found");
248+
}
249+
250+
fseek(f, from, SEEK_SET);
251+
void *buf = malloc(preview_length);
252+
if (buf == nullptr) {
253+
throw std::runtime_error("Can't allocate buffer!");
254+
255+
}
256+
size_t result = fread(buf, 1, preview_length, f);
257+
if (result != preview_length) {
258+
free(buf);
259+
throw std::runtime_error("File reading error!");
260+
}
261+
fclose(f);
262+
263+
Magick::Blob blob;//(buf, preview_length);
264+
blob.updateNoCopy(buf, preview_length, Magick::Blob::MallocAllocator);
265+
image->read(blob);
266+
267+
//free(buf); // should no free when updateNoCopy() from Magick::Blob used
268+
}
269+
179270
void Image::read_raw_with_libraw(const std::string &file, const bool half_size)
180271
{
181272
assert(raw_processor != 0);

src/image.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
/// img5 = new Image('photo.cr2', Image::ProcessingMode::Preview); // same as img2
2525
///
2626
/// // From memory
27-
/// img6 = new Image(buffer_ptr, buffer_size); // load blob from memory (blob is content of file), guess format with ImageMagick
27+
/// img6 = new Image(buffer_ptr, buffer_size); // load blob from memory (blob is content of file), guess format with GraphicsMagick
2828
/// img7 = new Image(buffer_ptr, buffer_size, Image::Format::RGB24);
2929
///
3030
/// // From other objects
@@ -59,9 +59,10 @@ class Image
5959
{
6060
public:
6161
enum RawProcessingMode {UndefinedRawProcessingMode, HalfRaw, FullRaw, TinyPreview, SmallPreview, FullPreview};
62+
enum JPEGProcessingMode {UndefinedJPEGProcessingMode, FullJpeg, PreviewJPEG};
6263

6364
Image();
64-
Image(const std::string &file, RawProcessingMode raw_processing_mode=FullPreview);
65+
Image(const std::string &file, RawProcessingMode raw_processing_mode=FullPreview, JPEGProcessingMode jpeg_processing_mode=FullJpeg);
6566
Image(const Magick::Image &image);
6667
Image(const Image &from_image);
6768
Image & operator = (const Image & other)
@@ -78,7 +79,7 @@ class Image
7879

7980
virtual ~Image();
8081

81-
void read(const std::string &file, RawProcessingMode raw_processing_mode=FullPreview);
82+
void read(const std::string &file, RawProcessingMode raw_processing_mode=FullPreview, JPEGProcessingMode jpeg_processing_mode=FullJpeg);
8283
void write(const std::string &new_file);
8384

8485
size_t to_buffer(void * &write_to_ptr);
@@ -155,6 +156,7 @@ class Image
155156

156157
void read_with_image_magick(const std::string &file);
157158
void read_preview_with_libraw(const std::string &file);
159+
void read_preview_from_jpeg(const std::string &file);
158160
void read_raw_with_libraw(const std::string &file, const bool half_size=false);
159161

160162
void delete_data();

src/main.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Q_DECLARE_METATYPE(Magick::Image*)
66

77
int main(int argc, char *argv[])
88
{
9-
Magick::InitializeMagick(*argv);
9+
Magick::InitializeMagick(*argv);
1010
QApplication a(argc, argv);
1111
MainWindow w;
1212
w.show();

0 commit comments

Comments
 (0)