OpenShot Library | libopenshot 0.5.0
Loading...
Searching...
No Matches
QtImageReader.cpp
Go to the documentation of this file.
1
8
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
28using namespace openshot;
29
30QtImageReader::QtImageReader(std::string path, bool inspect_reader) : path{QString::fromStdString(path)}, is_open(false)
31{
32 // Open and Close the reader, to populate its attributes (such as height, width, etc...)
33 if (inspect_reader) {
34 Open();
35 Close();
36 }
37}
38
43
44// Open image file
46{
47 // Open reader if not already open
48 if (!is_open)
49 {
50 bool loaded = false;
51 QSize default_svg_size;
52
53 // Check for SVG files and rasterizing them to QImages
54 if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
55 #if RESVG_VERSION_MIN(0, 11)
56 // Initialize the Resvg options
57 resvg_options.loadSystemFonts();
58 #endif
59
60 // Parse SVG file
61 default_svg_size = load_svg_path(path);
62 if (!default_svg_size.isEmpty()) {
63 loaded = true;
64 }
65 }
66
67 if (!loaded) {
68 // Attempt to open file using Qt's build in image processing capabilities
69 // AutoTransform enables exif data to be parsed and auto transform the image
70 // to the correct orientation
71 image = std::make_shared<QImage>();
72 QImageReader imgReader( path );
73 imgReader.setAutoTransform( true );
74 imgReader.setDecideFormatFromContent( true );
75 loaded = imgReader.read(image.get());
76 }
77
78 if (!loaded) {
79 // raise exception
80 throw InvalidFile("File could not be opened.", path.toStdString());
81 }
82
83 // Update image properties
84 info.has_audio = false;
85 info.has_video = true;
86 info.has_single_image = true;
87 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
88 // byteCount() is deprecated from Qt 5.10
89 info.file_size = image->sizeInBytes();
90 #else
91 info.file_size = image->byteCount();
92 #endif
93 info.vcodec = "QImage";
94 if (!default_svg_size.isEmpty()) {
95 // Use default SVG size (if detected)
96 info.width = default_svg_size.width();
97 info.height = default_svg_size.height();
98 } else {
99 // Use Qt Image size as a fallback
100 info.width = image->width();
101 info.height = image->height();
102 }
103 info.pixel_ratio.num = 1;
104 info.pixel_ratio.den = 1;
105 info.fps.num = 30;
106 info.fps.den = 1;
107 info.video_timebase.num = 1;
108 info.video_timebase.den = 30;
109 // Default still-image duration: 1 hour, aligned to fps
110 info.video_length = 60 * 60 * info.fps.num; // 3600 seconds * 30 fps
111 info.duration = static_cast<float>(info.video_length / info.fps.ToDouble());
112
113 // Calculate the DAR (display aspect ratio)
114 Fraction size(info.width * info.pixel_ratio.num, info.height * info.pixel_ratio.den);
115
116 // Reduce size fraction
117 size.Reduce();
118
119 // Set the ratio based on the reduced fraction
120 info.display_ratio.num = size.num;
121 info.display_ratio.den = size.den;
122
123 // Set current max size
124 max_size.setWidth(info.width);
125 max_size.setHeight(info.height);
126
127 // Mark as "open"
128 is_open = true;
129 }
130}
131
132// Close image file
134{
135 // Close all objects, if reader is 'open'
136 if (is_open)
137 {
138 // Mark as "closed"
139 is_open = false;
140
141 // Delete the image
142 image.reset();
143 cached_image.reset();
144
145 info.vcodec = "";
146 info.acodec = "";
147 }
148}
149
150// Get an openshot::Frame object for a specific frame number of this reader.
151std::shared_ptr<Frame> QtImageReader::GetFrame(int64_t requested_frame)
152{
153 // Check for open reader (or throw exception)
154 if (!is_open)
155 throw ReaderClosed("The Image is closed. Call Open() before calling this method.", path.toStdString());
156
157 // Create a scoped lock, allowing only a single thread to run the following code at one time
158 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
159
160 // Calculate max image size
161 QSize current_max_size = calculate_max_size();
162
163 // Scale image smaller (or use a previous scaled image)
164 if (!cached_image || max_size != current_max_size) {
165 // Check for SVG files and rasterize them to QImages
166 if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
167 load_svg_path(path);
168 }
169
170 // We need to resize the original image to a smaller image (for performance reasons)
171 // Only do this once, to prevent tons of unneeded scaling operations
172 cached_image = std::make_shared<QImage>(image->scaled(
173 current_max_size,
174 Qt::KeepAspectRatio, Qt::SmoothTransformation));
175
176 // Set max size (to later determine if max_size is changed)
177 max_size = current_max_size;
178 }
179
180 auto sample_count = Frame::GetSamplesPerFrame(
181 requested_frame, info.fps, info.sample_rate, info.channels);
182 auto sz = cached_image->size();
183
184 // Create frame object
185 auto image_frame = std::make_shared<Frame>(
186 requested_frame, sz.width(), sz.height(), "#000000",
187 sample_count, info.channels);
188 image_frame->AddImage(cached_image);
189
190 // return frame object
191 return image_frame;
192}
193
194// Calculate the max_size QSize, based on parent timeline and parent clip settings
195QSize QtImageReader::calculate_max_size() {
196 // Get max project size
197 int max_width = info.width;
198 int max_height = info.height;
199 if (max_width == 0 || max_height == 0) {
200 // If no size determined yet
201 max_width = 1920;
202 max_height = 1080;
203 }
204
205 Clip* parent = (Clip*) ParentClip();
206 if (parent) {
207 if (parent->ParentTimeline()) {
208 // Set max width/height based on parent clip's timeline (if attached to a timeline)
209 max_width = parent->ParentTimeline()->preview_width;
210 max_height = parent->ParentTimeline()->preview_height;
211 }
212 if (parent->scale == SCALE_FIT || parent->scale == SCALE_STRETCH) {
213 // Best fit or Stretch scaling (based on max timeline size * scaling keyframes)
214 float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
215 float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
216 max_width = std::max(float(max_width), max_width * max_scale_x);
217 max_height = std::max(float(max_height), max_height * max_scale_y);
218
219 } else if (parent->scale == SCALE_CROP) {
220 // Cropping scale mode (based on max timeline size * cropped size * scaling keyframes)
221 float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
222 float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
223 QSize width_size(max_width * max_scale_x,
224 round(max_width / (float(info.width) / float(info.height))));
225 QSize height_size(round(max_height / (float(info.height) / float(info.width))),
226 max_height * max_scale_y);
227 // respect aspect ratio
228 if (width_size.width() >= max_width && width_size.height() >= max_height) {
229 max_width = std::max(max_width, width_size.width());
230 max_height = std::max(max_height, width_size.height());
231 } else {
232 max_width = std::max(max_width, height_size.width());
233 max_height = std::max(max_height, height_size.height());
234 }
235 } else if (parent->scale == SCALE_NONE) {
236 // Scale images to equivalent unscaled size
237 // Since the preview window can change sizes, we want to always
238 // scale against the ratio of original image size to timeline size
239 float preview_ratio = 1.0;
240 if (parent->ParentTimeline()) {
241 Timeline *t = (Timeline *) parent->ParentTimeline();
242 preview_ratio = t->preview_width / float(t->info.width);
243 }
244 float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
245 float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
246 max_width = info.width * max_scale_x * preview_ratio;
247 max_height = info.height * max_scale_y * preview_ratio;
248 }
249
250 // If a crop effect is resizing the image, request enough pixels to preserve detail
251 ApplyCropResizeScale(parent, info.width, info.height, max_width, max_height);
252 }
253
254 // Return new QSize of the current max size
255 return QSize(max_width, max_height);
256}
257
258// Load an SVG file with Resvg or fallback with Qt
259QSize QtImageReader::load_svg_path(QString) {
260 bool loaded = false;
261 QSize default_size(0,0);
262
263 // Calculate max image size
264 QSize current_max_size = calculate_max_size();
265
266// Try to use libresvg for parsing/rasterizing SVG, if available
267#if RESVG_VERSION_MIN(0, 11)
268 ResvgRenderer renderer(path, resvg_options);
269 if (renderer.isValid()) {
270 default_size = renderer.defaultSize();
271 // Scale SVG size to keep aspect ratio, and fill max_size as much as possible
272 QSize svg_size = default_size.scaled(current_max_size, Qt::KeepAspectRatio);
273 auto qimage = renderer.renderToImage(svg_size);
274 image = std::make_shared<QImage>(
275 qimage.convertToFormat(QImage::Format_RGBA8888_Premultiplied));
276 loaded = true;
277 }
278#elif RESVG_VERSION_MIN(0, 0)
279 ResvgRenderer renderer(path);
280 if (renderer.isValid()) {
281 default_size = renderer.defaultSize();
282 // Scale SVG size to keep aspect ratio, and fill max_size as much as possible
283 QSize svg_size = default_size.scaled(current_max_size, Qt::KeepAspectRatio);
284 // Load SVG at max size
285 image = std::make_shared<QImage>(svg_size,
286 QImage::Format_RGBA8888_Premultiplied);
287 image->fill(Qt::transparent);
288 QPainter p(image.get());
289 renderer.render(&p);
290 p.end();
291 loaded = true;
292 }
293#endif // Resvg
294
295 if (!loaded) {
296 // Use Qt for parsing/rasterizing SVG
297 image = std::make_shared<QImage>();
298 loaded = image->load(path);
299
300 if (loaded) {
301 // Set default SVG size
302 default_size.setWidth(image->width());
303 default_size.setHeight(image->height());
304
305 if (image->width() < current_max_size.width() || image->height() < current_max_size.height()) {
306 // Load SVG into larger/project size (so image is not blurry)
307 QSize svg_size = image->size().scaled(
308 current_max_size, Qt::KeepAspectRatio);
309 if (QCoreApplication::instance()) {
310 // Requires QApplication to be running (for QPixmap support)
311 // Re-rasterize SVG image to max size
312 image = std::make_shared<QImage>(QIcon(path).pixmap(svg_size).toImage());
313 } else {
314 // Scale image without re-rasterizing it (due to lack of QApplication)
315 image = std::make_shared<QImage>(image->scaled(
316 svg_size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
317 }
318 }
319 }
320 }
321
322 return default_size;
323}
324
325// Generate JSON string of this object
326std::string QtImageReader::Json() const {
327
328 // Return formatted string
329 return JsonValue().toStyledString();
330}
331
332// Generate Json::Value for this object
333Json::Value QtImageReader::JsonValue() const {
334
335 // Create root json object
336 Json::Value root = ReaderBase::JsonValue(); // get parent properties
337 root["type"] = "QtImageReader";
338 root["path"] = path.toStdString();
339
340 // return JsonValue
341 return root;
342}
343
344// Load JSON string into this object
345void QtImageReader::SetJson(const std::string value) {
346
347 // Parse JSON string into JSON objects
348 try
349 {
350 const Json::Value root = openshot::stringToJson(value);
351 // Set all values that match
352 SetJsonValue(root);
353 }
354 catch (const std::exception& e)
355 {
356 // Error parsing JSON (or missing keys)
357 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
358 }
359}
360
361// Load Json::Value into this object
362void QtImageReader::SetJsonValue(const Json::Value root) {
363
364 // Set parent data
366
367 // Set data from Json (if key is found)
368 if (!root["path"].isNull())
369 path = QString::fromStdString(root["path"].asString());
370
371 // Re-Open path, and re-init everything (if needed)
372 if (is_open)
373 {
374 Close();
375 Open();
376 }
377}
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:316
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any).
Definition Clip.h:294
openshot::Keyframe scale_y
Curve representing the vertical scaling in percent (0 to 1).
Definition Clip.h:317
openshot::ScaleType scale
The scale determines how a clip should be resized to fit its parent.
Definition Clip.h:177
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
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:484
Exception for files that can not be found or opened.
Definition Exceptions.h:188
Exception for invalid JSON.
Definition Exceptions.h:218
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.
QtImageReader(std::string path, bool inspect_reader=true)
Constructor for QtImageReader.
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:88
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
Definition ReaderBase.h:79
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL).
Exception when a reader is closed, and a frame is requested.
Definition Exceptions.h:364
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
This namespace is the default namespace for all code in the openshot library.
Definition Compressor.h:29
@ 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.
const Json::Value stringToJson(const std::string value)
Definition Json.cpp:16
int width
The width of the video (in pixesl).
Definition ReaderBase.h:46
int height
The height of the video (in pixels).
Definition ReaderBase.h:45