OpenShot Library | libopenshot  0.6.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  // 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
259 QSize 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
326 std::string QtImageReader::Json() const {
327 
328  // Return formatted string
329  return JsonValue().toStyledString();
330 }
331 
332 // Generate Json::Value for this object
333 Json::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
345 void 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
362 void 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 }
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:318
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition: Clip.h:296
openshot::Keyframe scale_y
Curve representing the vertical scaling in percent (0 to 1)
Definition: Clip.h:319
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:88
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ReaderBase.cpp:157
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:106
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)
Definition: ReaderBase.cpp:240
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:46
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:45
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