1 /**
2 * @file
3 * @brief Source file for QtImageReader class
4 * @author Jonathan Thomas <jonathan@openshot.org>
5 *
6 * @ref License
7 */
8
9 /* LICENSE
10 *
11 * Copyright (c) 2008-2019 OpenShot Studios, LLC
12 * <http://www.openshotstudios.com/>. This file is part of
13 * OpenShot Library (libopenshot), an open-source project dedicated to
14 * delivering high quality video editing and animation solutions to the
15 * world. For more information visit <http://www.openshot.org/>.
16 *
17 * OpenShot Library (libopenshot) is free software: you can redistribute it
18 * and/or modify it under the terms of the GNU Lesser General Public License
19 * as published by the Free Software Foundation, either version 3 of the
20 * License, or (at your option) any later version.
21 *
22 * OpenShot Library (libopenshot) is distributed in the hope that it will be
23 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU Lesser General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public License
28 * along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
29 */
30
31 #include "QtImageReader.h"
32 #include "Exceptions.h"
33 #include "Settings.h"
34 #include "Clip.h"
35 #include "CacheMemory.h"
36 #include "Timeline.h"
37 #include <QtCore/QString>
38 #include <QtGui/QImage>
39 #include <QtGui/QPainter>
40 #include <QtGui/QIcon>
41 #include <QtGui/QImageReader>
42
43 #if USE_RESVG == 1
44 // If defined and found in CMake, utilize the libresvg for parsing
45 // SVG files and rasterizing them to QImages.
46 #include "ResvgQt.h"
47 #endif
48
49 using namespace openshot;
50
QtImageReader(std::string path,bool inspect_reader)51 QtImageReader::QtImageReader(std::string path, bool inspect_reader) : path{QString::fromStdString(path)}, is_open(false)
52 {
53 // Open and Close the reader, to populate its attributes (such as height, width, etc...)
54 if (inspect_reader) {
55 Open();
56 Close();
57 }
58 }
59
~QtImageReader()60 QtImageReader::~QtImageReader()
61 {
62 }
63
64 // Open image file
Open()65 void QtImageReader::Open()
66 {
67 // Open reader if not already open
68 if (!is_open)
69 {
70 bool loaded = false;
71 QSize default_svg_size;
72
73 // Check for SVG files and rasterizing them to QImages
74 if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
75 default_svg_size = load_svg_path(path);
76 if (!default_svg_size.isEmpty()) {
77 loaded = true;
78 }
79 }
80
81 if (!loaded) {
82 // Attempt to open file using Qt's build in image processing capabilities
83 // AutoTransform enables exif data to be parsed and auto transform the image
84 // to the correct orientation
85 image = std::make_shared<QImage>();
86 QImageReader imgReader( path );
87 imgReader.setAutoTransform( true );
88 loaded = imgReader.read(image.get());
89 }
90
91 if (!loaded) {
92 // raise exception
93 throw InvalidFile("File could not be opened.", path.toStdString());
94 }
95
96 // Update image properties
97 info.has_audio = false;
98 info.has_video = true;
99 info.has_single_image = true;
100 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
101 // byteCount() is deprecated from Qt 5.10
102 info.file_size = image->sizeInBytes();
103 #else
104 info.file_size = image->byteCount();
105 #endif
106 info.vcodec = "QImage";
107 if (!default_svg_size.isEmpty()) {
108 // Use default SVG size (if detected)
109 info.width = default_svg_size.width();
110 info.height = default_svg_size.height();
111 } else {
112 // Use Qt Image size as a fallback
113 info.width = image->width();
114 info.height = image->height();
115 }
116 info.pixel_ratio.num = 1;
117 info.pixel_ratio.den = 1;
118 info.duration = 60 * 60 * 1; // 1 hour duration
119 info.fps.num = 30;
120 info.fps.den = 1;
121 info.video_timebase.num = 1;
122 info.video_timebase.den = 30;
123 info.video_length = round(info.duration * info.fps.ToDouble());
124
125 // Calculate the DAR (display aspect ratio)
126 Fraction size(info.width * info.pixel_ratio.num, info.height * info.pixel_ratio.den);
127
128 // Reduce size fraction
129 size.Reduce();
130
131 // Set the ratio based on the reduced fraction
132 info.display_ratio.num = size.num;
133 info.display_ratio.den = size.den;
134
135 // Set current max size
136 max_size.setWidth(info.width);
137 max_size.setHeight(info.height);
138
139 // Mark as "open"
140 is_open = true;
141 }
142 }
143
144 // Close image file
Close()145 void QtImageReader::Close()
146 {
147 // Close all objects, if reader is 'open'
148 if (is_open)
149 {
150 // Mark as "closed"
151 is_open = false;
152
153 // Delete the image
154 image.reset();
155
156 info.vcodec = "";
157 info.acodec = "";
158 }
159 }
160
161 // Get an openshot::Frame object for a specific frame number of this reader.
GetFrame(int64_t requested_frame)162 std::shared_ptr<Frame> QtImageReader::GetFrame(int64_t requested_frame)
163 {
164 // Check for open reader (or throw exception)
165 if (!is_open)
166 throw ReaderClosed("The Image is closed. Call Open() before calling this method.", path.toStdString());
167
168 // Create a scoped lock, allowing only a single thread to run the following code at one time
169 const GenericScopedLock<CriticalSection> lock(getFrameCriticalSection);
170
171 // Calculate max image size
172 QSize current_max_size = calculate_max_size();
173
174 // Scale image smaller (or use a previous scaled image)
175 if (!cached_image || (max_size.width() != current_max_size.width() || max_size.height() != current_max_size.height())) {
176 // Check for SVG files and rasterize them to QImages
177 if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
178 load_svg_path(path);
179 }
180
181 // We need to resize the original image to a smaller image (for performance reasons)
182 // Only do this once, to prevent tons of unneeded scaling operations
183 cached_image = std::make_shared<QImage>(image->scaled(
184 current_max_size.width(), current_max_size.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
185
186 // Set max size (to later determine if max_size is changed)
187 max_size.setWidth(current_max_size.width());
188 max_size.setHeight(current_max_size.height());
189 }
190
191 // Create or get frame object
192 auto image_frame = std::make_shared<Frame>(
193 requested_frame, cached_image->width(), cached_image->height(), "#000000",
194 Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels),
195 info.channels);
196
197 // Add Image data to frame
198 image_frame->AddImage(cached_image);
199
200 // return frame object
201 return image_frame;
202 }
203
204 // Calculate the max_size QSize, based on parent timeline and parent clip settings
calculate_max_size()205 QSize QtImageReader::calculate_max_size() {
206 // Get max project size
207 int max_width = info.width;
208 int max_height = info.height;
209 if (max_width == 0 || max_height == 0) {
210 // If no size determined yet
211 max_width = 1920;
212 max_height = 1080;
213 }
214
215 Clip* parent = (Clip*) ParentClip();
216 if (parent) {
217 if (parent->ParentTimeline()) {
218 // Set max width/height based on parent clip's timeline (if attached to a timeline)
219 max_width = parent->ParentTimeline()->preview_width;
220 max_height = parent->ParentTimeline()->preview_height;
221 }
222 if (parent->scale == SCALE_FIT || parent->scale == SCALE_STRETCH) {
223 // Best fit or Stretch scaling (based on max timeline 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 max_width = std::max(float(max_width), max_width * max_scale_x);
227 max_height = std::max(float(max_height), max_height * max_scale_y);
228
229 } else if (parent->scale == SCALE_CROP) {
230 // Cropping scale mode (based on max timeline size * cropped size * scaling keyframes)
231 float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
232 float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
233 QSize width_size(max_width * max_scale_x,
234 round(max_width / (float(info.width) / float(info.height))));
235 QSize height_size(round(max_height / (float(info.height) / float(info.width))),
236 max_height * max_scale_y);
237 // respect aspect ratio
238 if (width_size.width() >= max_width && width_size.height() >= max_height) {
239 max_width = std::max(max_width, width_size.width());
240 max_height = std::max(max_height, width_size.height());
241 } else {
242 max_width = std::max(max_width, height_size.width());
243 max_height = std::max(max_height, height_size.height());
244 }
245 } else if (parent->scale == SCALE_NONE) {
246 // Scale images to equivalent unscaled size
247 // Since the preview window can change sizes, we want to always
248 // scale against the ratio of original image size to timeline size
249 float preview_ratio = 1.0;
250 if (parent->ParentTimeline()) {
251 Timeline *t = (Timeline *) parent->ParentTimeline();
252 preview_ratio = t->preview_width / float(t->info.width);
253 }
254 float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
255 float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
256 max_width = info.width * max_scale_x * preview_ratio;
257 max_height = info.height * max_scale_y * preview_ratio;
258 }
259 }
260
261 // Return new QSize of the current max size
262 return QSize(max_width, max_height);
263 }
264
265 // Load an SVG file with Resvg or fallback with Qt
load_svg_path(QString)266 QSize QtImageReader::load_svg_path(QString) {
267 bool loaded = false;
268 QSize default_size(0,0);
269
270 // Calculate max image size
271 QSize current_max_size = calculate_max_size();
272
273 #if USE_RESVG == 1
274 // Use libresvg for parsing/rasterizing SVG
275 ResvgRenderer renderer(path);
276 if (renderer.isValid()) {
277 // Set default SVG size
278 default_size.setWidth(renderer.defaultSize().width());
279 default_size.setHeight(renderer.defaultSize().height());
280
281 // Scale SVG size to keep aspect ratio, and fill the max_size as best as possible
282 QSize svg_size(default_size.width(), default_size.height());
283 svg_size.scale(current_max_size.width(), current_max_size.height(), Qt::KeepAspectRatio);
284
285 // Load SVG at max size
286 image = std::make_shared<QImage>(svg_size, 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
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(current_max_size.width(), current_max_size.height(), Qt::KeepAspectRatio);
308 if (QCoreApplication::instance()) {
309 // Requires QApplication to be running (for QPixmap support)
310 // Re-rasterize SVG image to max size
311 image = std::make_shared<QImage>(QIcon(path).pixmap(svg_size).toImage());
312 } else {
313 // Scale image without re-rasterizing it (due to lack of QApplication)
314 image = std::make_shared<QImage>(image->scaled(
315 svg_size.width(), svg_size.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
316 }
317 }
318 }
319 }
320
321 return default_size;
322 }
323
324 // Generate JSON string of this object
Json() const325 std::string QtImageReader::Json() const {
326
327 // Return formatted string
328 return JsonValue().toStyledString();
329 }
330
331 // Generate Json::Value for this object
JsonValue() const332 Json::Value QtImageReader::JsonValue() const {
333
334 // Create root json object
335 Json::Value root = ReaderBase::JsonValue(); // get parent properties
336 root["type"] = "QtImageReader";
337 root["path"] = path.toStdString();
338
339 // return JsonValue
340 return root;
341 }
342
343 // Load JSON string into this object
SetJson(const std::string value)344 void QtImageReader::SetJson(const std::string value) {
345
346 // Parse JSON string into JSON objects
347 try
348 {
349 const Json::Value root = openshot::stringToJson(value);
350 // Set all values that match
351 SetJsonValue(root);
352 }
353 catch (const std::exception& e)
354 {
355 // Error parsing JSON (or missing keys)
356 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
357 }
358 }
359
360 // Load Json::Value into this object
SetJsonValue(const Json::Value root)361 void QtImageReader::SetJsonValue(const Json::Value root) {
362
363 // Set parent data
364 ReaderBase::SetJsonValue(root);
365
366 // Set data from Json (if key is found)
367 if (!root["path"].isNull())
368 path = QString::fromStdString(root["path"].asString());
369
370 // Re-Open path, and re-init everything (if needed)
371 if (is_open)
372 {
373 Close();
374 Open();
375 }
376 }
377