OpenShot Library | libopenshot  0.7.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 
28 using namespace openshot;
29 
30 QtImageReader::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 
40 {
41  Close();
42 }
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("QtImageReader could not open image file.", 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)
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.
151 std::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
195 QSize 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  if (HasMaxDecodeSize()) {
255  QSize bounded_size(max_width, max_height);
256  const QSize max_decode_size(MaxDecodeWidth(), MaxDecodeHeight());
257  if (bounded_size.width() > max_decode_size.width() ||
258  bounded_size.height() > max_decode_size.height()) {
259  bounded_size.scale(max_decode_size, Qt::KeepAspectRatio);
260  max_width = bounded_size.width();
261  max_height = bounded_size.height();
262  }
263  }
264 
265  // Return new QSize of the current max size
266  return QSize(max_width, max_height);
267 }
268 
269 // Load an SVG file with Resvg or fallback with Qt
270 QSize QtImageReader::load_svg_path(QString) {
271  bool loaded = false;
272  QSize default_size(0,0);
273 
274  // Calculate max image size
275  QSize current_max_size = calculate_max_size();
276 
277 // Try to use libresvg for parsing/rasterizing SVG, if available
278 #if RESVG_VERSION_MIN(0, 11)
279  ResvgRenderer renderer(path, resvg_options);
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  auto qimage = renderer.renderToImage(svg_size);
285  image = std::make_shared<QImage>(
286  qimage.convertToFormat(QImage::Format_RGBA8888_Premultiplied));
287  loaded = true;
288  }
289 #elif RESVG_VERSION_MIN(0, 0)
290  ResvgRenderer renderer(path);
291  if (renderer.isValid()) {
292  default_size = renderer.defaultSize();
293  // Scale SVG size to keep aspect ratio, and fill max_size as much as possible
294  QSize svg_size = default_size.scaled(current_max_size, Qt::KeepAspectRatio);
295  // Load SVG at max size
296  image = std::make_shared<QImage>(svg_size,
297  QImage::Format_RGBA8888_Premultiplied);
298  image->fill(Qt::transparent);
299  QPainter p(image.get());
300  renderer.render(&p);
301  p.end();
302  loaded = true;
303  }
304 #endif // Resvg
305 
306  if (!loaded) {
307  // Use Qt for parsing/rasterizing SVG
308  image = std::make_shared<QImage>();
309  loaded = image->load(path);
310 
311  if (loaded) {
312  // Set default SVG size
313  default_size.setWidth(image->width());
314  default_size.setHeight(image->height());
315 
316  if (image->width() < current_max_size.width() || image->height() < current_max_size.height()) {
317  // Load SVG into larger/project size (so image is not blurry)
318  QSize svg_size = image->size().scaled(
319  current_max_size, Qt::KeepAspectRatio);
320  if (QCoreApplication::instance()) {
321  // Requires QApplication to be running (for QPixmap support)
322  // Re-rasterize SVG image to max size
323  image = std::make_shared<QImage>(QIcon(path).pixmap(svg_size).toImage());
324  } else {
325  // Scale image without re-rasterizing it (due to lack of QApplication)
326  image = std::make_shared<QImage>(image->scaled(
327  svg_size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
328  }
329  }
330  }
331  }
332 
333  return default_size;
334 }
335 
336 // Generate JSON string of this object
337 std::string QtImageReader::Json() const {
338 
339  // Return formatted string
340  return JsonValue().toStyledString();
341 }
342 
343 // Generate Json::Value for this object
344 Json::Value QtImageReader::JsonValue() const {
345 
346  // Create root json object
347  Json::Value root = ReaderBase::JsonValue(); // get parent properties
348  root["type"] = "QtImageReader";
349  root["path"] = path.toStdString();
350 
351  // return JsonValue
352  return root;
353 }
354 
355 // Load JSON string into this object
356 void QtImageReader::SetJson(const std::string value) {
357 
358  // Parse JSON string into JSON objects
359  try
360  {
361  const Json::Value root = openshot::stringToJson(value);
362  // Set all values that match
363  SetJsonValue(root);
364  }
365  catch (const std::exception& e)
366  {
367  // Error parsing JSON (or missing keys)
368  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
369  }
370 }
371 
372 // Load Json::Value into this object
373 void QtImageReader::SetJsonValue(const Json::Value root) {
374 
375  // Set parent data
377 
378  // Set data from Json (if key is found)
379  if (!root["path"].isNull())
380  path = QString::fromStdString(root["path"].asString());
381 }
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:322
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition: Clip.h:300
openshot::Keyframe scale_y
Curve representing the vertical scaling in percent (0 to 1)
Definition: Clip.h:323
openshot::ScaleType scale
The scale determines how a clip should be resized to fit its parent.
Definition: Clip.h:179
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:484
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:90
int MaxDecodeWidth() const
Return the current maximum decoded frame width (0 when unlimited).
Definition: ReaderBase.cpp:257
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ReaderBase.cpp:160
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:109
int MaxDecodeHeight() const
Return the current maximum decoded frame height (0 when unlimited).
Definition: ReaderBase.cpp:261
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:265
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL)
Definition: ReaderBase.cpp:243
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: 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.
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