OpenShot Library | libopenshot 1.1.1
QtImageReader.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2008-2019 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include "QtImageReader.h"
14
15#include "Clip.h"
16#include "CacheMemory.h"
17#include "Exceptions.h"
18#include "Timeline.h"
19#include "effects/CropHelpers.h"
20
21#include <algorithm>
22#include <QString>
23#include <QImage>
24#include <QPainter>
25#include <QIcon>
26#include <QImageReader>
27#if USE_RESVG != 1
28#include <QSvgRenderer>
29#endif
30
31using namespace openshot;
32
33QtImageReader::QtImageReader(std::string path, bool inspect_reader) : path{QString::fromStdString(path)}, is_open(false)
34{
35 // Open and Close the reader, to populate its attributes (such as height, width, etc...)
36 if (inspect_reader) {
37 Open();
38 Close();
39 }
40}
41
43{
44 Close();
45}
46
47// Open image file
49{
50 // Open reader if not already open
51 if (!is_open)
52 {
53 bool loaded = false;
54 QSize default_svg_size;
55
56 // Check for SVG files and rasterizing them to QImages
57 if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
58 #if RESVG_VERSION_MIN(0, 11)
59 // Initialize the Resvg options
60 resvg_options.loadSystemFonts();
61 #endif
62
63 // Parse SVG file
64 default_svg_size = load_svg_path(path);
65 if (!default_svg_size.isEmpty()) {
66 loaded = true;
67 }
68 }
69
70 if (!loaded) {
71 // Attempt to open file using Qt's build in image processing capabilities
72 // AutoTransform enables exif data to be parsed and auto transform the image
73 // to the correct orientation
74 image = std::make_shared<QImage>();
75 QImageReader imgReader( path );
76 imgReader.setAutoTransform( true );
77 imgReader.setDecideFormatFromContent( true );
78 loaded = imgReader.read(image.get());
79 }
80
81 if (!loaded) {
82 // raise exception
83 throw InvalidFile("QtImageReader could not open image file.", path.toStdString());
84 }
85
86 // Update image properties
87 info.has_audio = false;
88 info.has_video = true;
90 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
91 // byteCount() is deprecated from Qt 5.10
92 info.file_size = image->sizeInBytes();
93 #else
94 info.file_size = image->byteCount();
95 #endif
96 info.vcodec = "QImage";
97 if (!default_svg_size.isEmpty()) {
98 // Use default SVG size (if detected)
99 info.width = default_svg_size.width();
100 info.height = default_svg_size.height();
101 } else {
102 // Use Qt Image size as a fallback
103 info.width = image->width();
104 info.height = image->height();
105 }
106 info.pixel_ratio.num = 1;
107 info.pixel_ratio.den = 1;
108 info.fps.num = 30;
109 info.fps.den = 1;
112 // Default still-image duration: 1 hour, aligned to fps
113 info.video_length = 60 * 60 * info.fps.num; // 3600 seconds * 30 fps
114 info.duration = static_cast<float>(info.video_length / info.fps.ToDouble());
115
116 // Calculate the DAR (display aspect ratio)
118
119 // Reduce size fraction
120 size.Reduce();
121
122 // Set the ratio based on the reduced fraction
123 info.display_ratio.num = size.num;
124 info.display_ratio.den = size.den;
125
126 // Set current max size
127 max_size.setWidth(info.width);
128 max_size.setHeight(info.height);
129
130 // Mark as "open"
131 is_open = true;
132 }
133}
134
135// Close image file
137{
138 // Close all objects, if reader is 'open'
139 if (is_open)
140 {
141 // Mark as "closed"
142 is_open = false;
143
144 // Delete the image
145 image.reset();
146 cached_image.reset();
147
148 info.vcodec = "";
149 info.acodec = "";
150 }
151}
152
153// Get an openshot::Frame object for a specific frame number of this reader.
154std::shared_ptr<Frame> QtImageReader::GetFrame(int64_t requested_frame)
155{
156 // Check for open reader (or throw exception)
157 if (!is_open)
158 throw ReaderClosed("The Image is closed. Call Open() before calling this method.", path.toStdString());
159
160 // Create a scoped lock, allowing only a single thread to run the following code at one time
161 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
162
163 // Calculate max image size
164 QSize current_max_size = calculate_max_size();
165
166 // Scale image smaller (or use a previous scaled image)
167 if (!cached_image || max_size != current_max_size) {
168 // Check for SVG files and rasterize them to QImages
169 if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
170 load_svg_path(path);
171 }
172
173 // We need to resize the original image to a smaller image (for performance reasons)
174 // Only do this once, to prevent tons of unneeded scaling operations
175 cached_image = std::make_shared<QImage>(image->scaled(
176 current_max_size,
177 Qt::KeepAspectRatio, Qt::SmoothTransformation));
178
179 // Set max size (to later determine if max_size is changed)
180 max_size = current_max_size;
181 }
182
183 auto sample_count = Frame::GetSamplesPerFrame(
184 requested_frame, info.fps, info.sample_rate, info.channels);
185 auto sz = cached_image->size();
186
187 // Create frame object
188 auto image_frame = std::make_shared<Frame>(
189 requested_frame, sz.width(), sz.height(), "#000000",
190 sample_count, info.channels);
191 image_frame->AddImage(cached_image);
192
193 // return frame object
194 return image_frame;
195}
196
197// Calculate the max_size QSize, based on parent timeline and parent clip settings
198QSize QtImageReader::calculate_max_size() {
199 // Get max project size
200 int max_width = info.width;
201 int max_height = info.height;
202 if (max_width == 0 || max_height == 0) {
203 // If no size determined yet
204 max_width = 1920;
205 max_height = 1080;
206 }
207
208 Clip* parent = (Clip*) ParentClip();
209 if (parent) {
210 if (parent->ParentTimeline()) {
211 // Set max width/height based on parent clip's timeline (if attached to a timeline)
212 max_width = parent->ParentTimeline()->preview_width;
213 max_height = parent->ParentTimeline()->preview_height;
214 }
215 if (parent->scale == SCALE_FIT || parent->scale == SCALE_STRETCH) {
216 // Best fit or Stretch scaling (based on max timeline size * scaling keyframes)
217 float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
218 float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
219 max_width = std::max(float(max_width), max_width * max_scale_x);
220 max_height = std::max(float(max_height), max_height * max_scale_y);
221
222 } else if (parent->scale == SCALE_CROP) {
223 // Cropping scale mode (based on max timeline size * cropped size * scaling keyframes)
224 float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
225 float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
226 QSize width_size(max_width * max_scale_x,
227 round(max_width / (float(info.width) / float(info.height))));
228 QSize height_size(round(max_height / (float(info.height) / float(info.width))),
229 max_height * max_scale_y);
230 // respect aspect ratio
231 if (width_size.width() >= max_width && width_size.height() >= max_height) {
232 max_width = std::max(max_width, width_size.width());
233 max_height = std::max(max_height, width_size.height());
234 } else {
235 max_width = std::max(max_width, height_size.width());
236 max_height = std::max(max_height, height_size.height());
237 }
238 } else if (parent->scale == SCALE_NONE) {
239 // Scale images to equivalent unscaled size
240 // Since the preview window can change sizes, we want to always
241 // scale against the ratio of original image size to timeline size
242 float preview_ratio = 1.0;
243 if (parent->ParentTimeline()) {
244 Timeline *t = (Timeline *) parent->ParentTimeline();
245 preview_ratio = t->preview_width / float(t->info.width);
246 }
247 float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
248 float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
249 max_width = info.width * max_scale_x * preview_ratio;
250 max_height = info.height * max_scale_y * preview_ratio;
251 }
252
253 // If a crop effect is resizing the image, request enough pixels to preserve detail
254 ApplyCropResizeScale(parent, info.width, info.height, max_width, max_height);
255 }
256
257 if (HasMaxDecodeSize()) {
258 QSize bounded_size(max_width, max_height);
259 const QSize max_decode_size(MaxDecodeWidth(), MaxDecodeHeight());
260 const QString lower_path = path.toLower();
261 const bool is_svg = lower_path.endsWith(".svg") || lower_path.endsWith(".svgz");
262
263 if (is_svg && !parent) {
264 // Vector images have no fixed source pixel limit. With no parent
265 // Timeline/Clip to provide a render size, use MaxDecodeSize as the
266 // rasterization target instead of preserving the tiny document size.
267 bounded_size.scale(max_decode_size, Qt::KeepAspectRatio);
268 max_width = bounded_size.width();
269 max_height = bounded_size.height();
270 } else if (bounded_size.width() > max_decode_size.width() ||
271 bounded_size.height() > max_decode_size.height()) {
272 bounded_size.scale(max_decode_size, Qt::KeepAspectRatio);
273 max_width = bounded_size.width();
274 max_height = bounded_size.height();
275 }
276 }
277
278 // Return new QSize of the current max size
279 return QSize(max_width, max_height);
280}
281
282// Load an SVG file with Resvg or fallback with Qt
283QSize QtImageReader::load_svg_path(QString) {
284 bool loaded = false;
285 QSize default_size(0,0);
286
287 // Calculate max image size
288 QSize current_max_size = calculate_max_size();
289
290// Try to use libresvg for parsing/rasterizing SVG, if available
291#if RESVG_VERSION_MIN(0, 11)
292 ResvgRenderer renderer(path, resvg_options);
293 if (renderer.isValid()) {
294 default_size = renderer.defaultSize();
295 // Scale SVG size to keep aspect ratio, and fill max_size as much as possible
296 QSize svg_size = default_size.scaled(current_max_size, Qt::KeepAspectRatio);
297 auto qimage = renderer.renderToImage(svg_size);
298 image = std::make_shared<QImage>(
299 qimage.convertToFormat(QImage::Format_RGBA8888_Premultiplied));
300 loaded = true;
301 }
302#elif RESVG_VERSION_MIN(0, 0)
303 ResvgRenderer renderer(path);
304 if (renderer.isValid()) {
305 default_size = renderer.defaultSize();
306 // Scale SVG size to keep aspect ratio, and fill max_size as much as possible
307 QSize svg_size = default_size.scaled(current_max_size, Qt::KeepAspectRatio);
308 // Load SVG at max size
309 image = std::make_shared<QImage>(svg_size,
310 QImage::Format_RGBA8888_Premultiplied);
311 image->fill(Qt::transparent);
312 QPainter p(image.get());
313 renderer.render(&p);
314 p.end();
315 loaded = true;
316 }
317#endif // Resvg
318
319 if (!loaded) {
320 // Use Qt for parsing/rasterizing SVG
321 image = std::make_shared<QImage>();
322 loaded = image->load(path);
323
324 if (loaded) {
325 // Set default SVG size
326 default_size.setWidth(image->width());
327 default_size.setHeight(image->height());
328
329 if (image->width() < current_max_size.width() || image->height() < current_max_size.height()) {
330 // Load SVG into larger/project size (so image is not blurry)
331 QSize svg_size = image->size().scaled(
332 current_max_size, Qt::KeepAspectRatio);
333 if (QCoreApplication::instance()) {
334#if USE_RESVG != 1
335 // Re-rasterize SVG directly into a QImage at the target size,
336 // instead of routing through QIcon/QPixmap which can apply
337 // device-pixel-ratio behavior we do not want in timeline frames.
338 QSvgRenderer renderer(path);
339 if (renderer.isValid()) {
340 image = std::make_shared<QImage>(
341 svg_size, QImage::Format_RGBA8888_Premultiplied);
342 image->fill(Qt::transparent);
343 QPainter p(image.get());
344 renderer.render(&p);
345 p.end();
346 } else {
347 image = std::make_shared<QImage>(image->scaled(
348 svg_size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
349 }
350#else
351 image = std::make_shared<QImage>(image->scaled(
352 svg_size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
353#endif
354 } else {
355 // Scale image without re-rasterizing it (due to lack of QApplication)
356 image = std::make_shared<QImage>(image->scaled(
357 svg_size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
358 }
359 }
360 }
361 }
362
363 return default_size;
364}
365
366// Generate JSON string of this object
367std::string QtImageReader::Json() const {
368
369 // Return formatted string
370 return JsonValue().toStyledString();
371}
372
373// Generate Json::Value for this object
374Json::Value QtImageReader::JsonValue() const {
375
376 // Create root json object
377 Json::Value root = ReaderBase::JsonValue(); // get parent properties
378 root["type"] = "QtImageReader";
379 root["path"] = path.toStdString();
380
381 // return JsonValue
382 return root;
383}
384
385// Load JSON string into this object
386void QtImageReader::SetJson(const std::string value) {
387
388 // Parse JSON string into JSON objects
389 try
390 {
391 const Json::Value root = openshot::stringToJson(value);
392 // Set all values that match
393 SetJsonValue(root);
394 }
395 catch (const std::exception& e)
396 {
397 // Error parsing JSON (or missing keys)
398 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
399 }
400}
401
402// Load Json::Value into this object
403void QtImageReader::SetJsonValue(const Json::Value root) {
404
405 // Set parent data
407
408 // Set data from Json (if key is found)
409 if (!root["path"].isNull())
410 path = QString::fromStdString(root["path"].asString());
411}
Header file for CacheMemory class.
Header file for Clip class.
Shared helpers for Crop effect scaling logic.
Header file for all Exception classes.
Header file for QtImageReader class.
Header file for Timeline class.
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
openshot::Keyframe scale_x
Curve representing the horizontal scaling in percent (0 to 1)
Definition: Clip.h:334
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition: Clip.h:310
openshot::Keyframe scale_y
Curve representing the vertical scaling in percent (0 to 1)
Definition: Clip.h:335
openshot::ScaleType scale
Definition: Clip.h:186
double Y
The Y value of the coordinate (usually representing the value of the property being animated)
Definition: Coordinate.h:41
This class represents a fraction.
Definition: Fraction.h:30
int num
Numerator for the fraction.
Definition: Fraction.h:32
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:40
void Reduce()
Reduce this fraction (i.e. 640/480 = 4/3)
Definition: Fraction.cpp:65
int den
Denominator for the fraction.
Definition: Fraction.h:33
int GetSamplesPerFrame(openshot::Fraction fps, int sample_rate, int channels)
Calculate the # of samples per video frame (for the current frame number)
Definition: Frame.cpp:486
Exception for files that can not be found or opened.
Definition: Exceptions.h:194
Exception for invalid JSON.
Definition: Exceptions.h:224
Point GetMaxPoint() const
Get max point (by Y coordinate)
Definition: KeyFrame.cpp:245
Coordinate co
This is the primary coordinate.
Definition: Point.h:66
Json::Value JsonValue() const override
Generate Json::Value for this object.
std::string Json() const override
Generate JSON string of this object.
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
void Open() override
Open File - which is called by the constructor automatically.
void SetJson(const std::string value) override
Load JSON string into this object.
void Close() override
Close File.
std::shared_ptr< Frame > GetFrame(int64_t requested_frame) override
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:91
int MaxDecodeWidth() const
Return the current maximum decoded frame width (0 when unlimited).
Definition: ReaderBase.cpp:258
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ReaderBase.cpp:161
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:110
int MaxDecodeHeight() const
Return the current maximum decoded frame height (0 when unlimited).
Definition: ReaderBase.cpp:262
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
Definition: ReaderBase.h:79
bool HasMaxDecodeSize() const
Return true when a maximum decoded frame size is active.
Definition: ReaderBase.cpp:266
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL)
Definition: ReaderBase.cpp:244
Exception when a reader is closed, and a frame is requested.
Definition: Exceptions.h:370
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:45
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:44
This class represents a timeline.
Definition: Timeline.h:154
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
@ SCALE_FIT
Scale the clip until either height or width fills the canvas (with no cropping)
Definition: Enums.h:38
@ SCALE_STRETCH
Scale the clip until both height and width fill the canvas (distort to fit)
Definition: Enums.h:39
@ SCALE_CROP
Scale the clip until both height and width fill the canvas (cropping the overlap)
Definition: Enums.h:37
@ SCALE_NONE
Do not scale the clip.
Definition: Enums.h:40
void ApplyCropResizeScale(Clip *clip, int source_width, int source_height, int &max_width, int &max_height)
Scale the requested max_width / max_height based on the Crop resize amount, capped by source size.
Definition: CropHelpers.cpp:40
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
bool has_single_image
Determines if this file only contains a single image.
Definition: ReaderBase.h:42
float duration
Length of time (in seconds)
Definition: ReaderBase.h:43
int width
The width of the video (in pixesl)
Definition: ReaderBase.h:46
int channels
The number of audio channels used in the audio stream.
Definition: ReaderBase.h:61
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
openshot::Fraction display_ratio
The ratio of width to height of the video stream (i.e. 640x480 has a ratio of 4/3)
Definition: ReaderBase.h:51
int height
The height of the video (in pixels)
Definition: ReaderBase.h:45
int64_t video_length
The number of frames in the video stream.
Definition: ReaderBase.h:53
std::string acodec
The name of the audio codec used to encode / decode the video stream.
Definition: ReaderBase.h:58
std::string vcodec
The name of the video codec used to encode / decode the video stream.
Definition: ReaderBase.h:52
openshot::Fraction pixel_ratio
The pixel ratio of the video stream as a fraction (i.e. some pixels are not square)
Definition: ReaderBase.h:50
bool has_video
Determines if this file has a video stream.
Definition: ReaderBase.h:40
bool has_audio
Determines if this file has an audio stream.
Definition: ReaderBase.h:41
openshot::Fraction video_timebase
The video timebase determines how long each frame stays on the screen.
Definition: ReaderBase.h:55
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
Definition: ReaderBase.h:60
int64_t file_size
Size of file (in bytes)
Definition: ReaderBase.h:44