OpenShot Library | libopenshot  1.0.0
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 
31 using namespace openshot;
32 
33 QtImageReader::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;
89  info.has_single_image = 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;
110  info.video_timebase.num = 1;
111  info.video_timebase.den = 30;
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.
154 std::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
198 QSize 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
283 QSize 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
367 std::string QtImageReader::Json() const {
368 
369  // Return formatted string
370  return JsonValue().toStyledString();
371 }
372 
373 // Generate Json::Value for this object
374 Json::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
386 void 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
403 void 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:331
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition: Clip.h:307
openshot::Keyframe scale_y
Curve representing the vertical scaling in percent (0 to 1)
Definition: Clip.h:332
openshot::ScaleType scale
The scale determines how a clip should be resized to fit its parent.
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.
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: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:153
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