From 10843a2c8cec6cd3bbb0b14a84fe1a19eee89808 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Tue, 16 Mar 2021 23:37:44 +0100 Subject: [PATCH] QuickShow: Animate animated images :^) With a little help (read: copy & paste) from ImageWidget, QuickShow will now cycle through the frames of animated images - enjoy the cat GIFs! Future improvement: cache decoded images like LibWeb's ImageResource to waste less CPU - the same applies to LibGUI though, maybe we can put something shared in LibGfx. Closes #5837. --- Userland/Applications/QuickShow/QSWidget.cpp | 56 ++++++++++++++++++-- Userland/Applications/QuickShow/QSWidget.h | 20 ++++--- Userland/Libraries/LibGUI/ImageWidget.cpp | 1 + 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/Userland/Applications/QuickShow/QSWidget.cpp b/Userland/Applications/QuickShow/QSWidget.cpp index 6cc2c125ab..7a52e43fe6 100644 --- a/Userland/Applications/QuickShow/QSWidget.cpp +++ b/Userland/Applications/QuickShow/QSWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Linus Groh * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,16 +26,18 @@ */ #include "QSWidget.h" +#include #include #include +#include #include #include -#include #include #include #include QSWidget::QSWidget() + : m_timer(Core::Timer::construct()) { set_fill_with_background_color(false); } @@ -244,12 +247,31 @@ void QSWidget::mousewheel_event(GUI::MouseEvent& event) void QSWidget::load_from_file(const String& path) { - auto bitmap = Gfx::Bitmap::load_from_file(path); - if (!bitmap) { + auto show_error = [&] { GUI::MessageBox::show(window(), String::formatted("Failed to open {}", path), "Cannot open image", GUI::MessageBox::Type::Error); + }; + + auto file_or_error = MappedFile::map(path); + if (file_or_error.is_error()) { + show_error(); return; } + auto& mapped_file = *file_or_error.value(); + m_image_decoder = Gfx::ImageDecoder::create((const u8*)mapped_file.data(), mapped_file.size()); + auto bitmap = m_image_decoder->bitmap(); + if (!bitmap) { + show_error(); + return; + } + + if (m_image_decoder->is_animated() && m_image_decoder->frame_count() > 1) { + const auto& first_frame = m_image_decoder->frame(0); + m_timer->set_interval(first_frame.duration); + m_timer->on_timeout = [this] { animate(); }; + m_timer->start(); + } + m_path = path; m_bitmap = bitmap; m_scale = -1; @@ -287,3 +309,31 @@ void QSWidget::reset_view() m_pan_origin = { 0, 0 }; set_scale(100); } + +void QSWidget::set_bitmap(const Gfx::Bitmap* bitmap) +{ + if (m_bitmap == bitmap) + return; + m_bitmap = bitmap; + update(); +} + +// Same as ImageWidget::animate(), you probably want to keep any changes in sync +void QSWidget::animate() +{ + m_current_frame_index = (m_current_frame_index + 1) % m_image_decoder->frame_count(); + + const auto& current_frame = m_image_decoder->frame(m_current_frame_index); + set_bitmap(current_frame.image); + + if (current_frame.duration != m_timer->interval()) { + m_timer->restart(current_frame.duration); + } + + if (m_current_frame_index == m_image_decoder->frame_count() - 1) { + ++m_loops_completed; + if (m_loops_completed > 0 && m_loops_completed == m_image_decoder->loop_count()) { + m_timer->stop(); + } + } +} diff --git a/Userland/Applications/QuickShow/QSWidget.h b/Userland/Applications/QuickShow/QSWidget.h index 57bc346f5b..c39ceb9f4b 100644 --- a/Userland/Applications/QuickShow/QSWidget.h +++ b/Userland/Applications/QuickShow/QSWidget.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Linus Groh * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,12 +27,12 @@ #pragma once +#include #include #include +#include #include -class QSLabel; - class QSWidget final : public GUI::Frame { C_OBJECT(QSWidget) public: @@ -72,18 +73,25 @@ private: virtual void mousewheel_event(GUI::MouseEvent&) override; virtual void drop_event(GUI::DropEvent&) override; + void set_bitmap(const Gfx::Bitmap* bitmap); + void relayout(); void resize_window(); void reset_view(); + void animate(); String m_path; RefPtr m_bitmap; - int m_toolbar_height { 28 }; - Gfx::IntRect m_bitmap_rect; - int m_scale { -1 }; - Gfx::FloatPoint m_pan_origin; + RefPtr m_image_decoder; + size_t m_current_frame_index { 0 }; + size_t m_loops_completed { 0 }; + NonnullRefPtr m_timer; + + int m_scale { -1 }; + int m_toolbar_height { 28 }; + Gfx::FloatPoint m_pan_origin; Gfx::IntPoint m_click_position; Gfx::FloatPoint m_saved_pan_origin; Vector m_files_in_same_dir; diff --git a/Userland/Libraries/LibGUI/ImageWidget.cpp b/Userland/Libraries/LibGUI/ImageWidget.cpp index 218c09a809..24f68398e7 100644 --- a/Userland/Libraries/LibGUI/ImageWidget.cpp +++ b/Userland/Libraries/LibGUI/ImageWidget.cpp @@ -71,6 +71,7 @@ void ImageWidget::set_auto_resize(bool value) set_fixed_size(m_bitmap->size()); } +// Same as QSWidget::animate(), you probably want to keep any changes in sync void ImageWidget::animate() { m_current_frame_index = (m_current_frame_index + 1) % m_image_decoder->frame_count();