123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- /*
- * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
- * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include "ViewWidget.h"
- #include <AK/LexicalPath.h>
- #include <AK/MappedFile.h>
- #include <AK/StringBuilder.h>
- #include <LibCore/DirIterator.h>
- #include <LibCore/File.h>
- #include <LibCore/Timer.h>
- #include <LibGUI/MessageBox.h>
- #include <LibGUI/Painter.h>
- #include <LibGfx/Bitmap.h>
- #include <LibGfx/Orientation.h>
- #include <LibGfx/Palette.h>
- #include <LibImageDecoderClient/Client.h>
- namespace ImageViewer {
- ViewWidget::ViewWidget()
- : m_timer(Core::Timer::construct())
- {
- set_fill_with_background_color(false);
- }
- ViewWidget::~ViewWidget()
- {
- }
- void ViewWidget::clear()
- {
- m_timer->stop();
- m_decoded_image.clear();
- m_bitmap = nullptr;
- if (on_image_change)
- on_image_change(m_bitmap);
- m_path = {};
- reset_view();
- update();
- }
- void ViewWidget::flip(Gfx::Orientation orientation)
- {
- m_bitmap = m_bitmap->flipped(orientation);
- set_scale(m_scale);
- resize_window();
- }
- void ViewWidget::rotate(Gfx::RotationDirection rotation_direction)
- {
- m_bitmap = m_bitmap->rotated(rotation_direction);
- set_scale(m_scale);
- resize_window();
- }
- void ViewWidget::navigate(Directions direction)
- {
- if (m_path == nullptr)
- return;
- auto current_dir = LexicalPath(m_path).parent().string();
- if (m_files_in_same_dir.is_empty()) {
- Core::DirIterator iterator(current_dir, Core::DirIterator::Flags::SkipDots);
- while (iterator.has_next()) {
- String file = iterator.next_full_path();
- if (!Gfx::Bitmap::is_path_a_supported_image_format(file))
- continue;
- m_files_in_same_dir.append(file);
- }
- }
- auto current_index = m_files_in_same_dir.find_first_index(m_path);
- if (!current_index.has_value()) {
- return;
- }
- size_t index = current_index.value();
- if (direction == Directions::Back) {
- if (index == 0) {
- GUI::MessageBox::show(window(), "This is the first file.", "Cannot open image", GUI::MessageBox::Type::Error);
- return;
- }
- index--;
- } else if (direction == Directions::Forward) {
- if (index == m_files_in_same_dir.size() - 1) {
- GUI::MessageBox::show(window(), "This is the last file.", "Cannot open image", GUI::MessageBox::Type::Error);
- return;
- }
- index++;
- } else if (direction == Directions::First) {
- index = 0;
- } else if (direction == Directions::Last) {
- index = m_files_in_same_dir.size() - 1;
- }
- this->load_from_file(m_files_in_same_dir.at(index));
- }
- void ViewWidget::set_scale(int scale)
- {
- if (m_bitmap.is_null())
- return;
- if (scale < 10)
- scale = 10;
- if (scale > 1000)
- scale = 1000;
- m_scale = scale;
- float scale_factor = (float)m_scale / 100.0f;
- Gfx::IntSize new_size;
- new_size.set_width(m_bitmap->width() * scale_factor);
- new_size.set_height(m_bitmap->height() * scale_factor);
- m_bitmap_rect.set_size(new_size);
- if (on_scale_change)
- on_scale_change(m_scale);
- relayout();
- }
- void ViewWidget::relayout()
- {
- if (m_bitmap.is_null())
- return;
- Gfx::IntSize new_size = m_bitmap_rect.size();
- Gfx::IntPoint new_location;
- new_location.set_x((width() / 2) - (new_size.width() / 2) - m_pan_origin.x());
- new_location.set_y((height() / 2) - (new_size.height() / 2) - m_pan_origin.y());
- m_bitmap_rect.set_location(new_location);
- update();
- }
- void ViewWidget::resize_event(GUI::ResizeEvent& event)
- {
- relayout();
- GUI::Widget::resize_event(event);
- }
- void ViewWidget::doubleclick_event(GUI::MouseEvent&)
- {
- on_doubleclick();
- }
- void ViewWidget::paint_event(GUI::PaintEvent& event)
- {
- Frame::paint_event(event);
- GUI::Painter painter(*this);
- painter.add_clip_rect(event.rect());
- painter.add_clip_rect(frame_inner_rect());
- Gfx::StylePainter::paint_transparency_grid(painter, frame_inner_rect(), palette());
- if (!m_bitmap.is_null())
- painter.draw_scaled_bitmap(m_bitmap_rect, *m_bitmap, m_bitmap->rect());
- }
- void ViewWidget::mousedown_event(GUI::MouseEvent& event)
- {
- if (event.button() != GUI::MouseButton::Left)
- return;
- m_click_position = event.position();
- m_saved_pan_origin = m_pan_origin;
- }
- void ViewWidget::mouseup_event([[maybe_unused]] GUI::MouseEvent& event) { }
- void ViewWidget::mousemove_event(GUI::MouseEvent& event)
- {
- if (!(event.buttons() & GUI::MouseButton::Left))
- return;
- auto delta = event.position() - m_click_position;
- m_pan_origin = m_saved_pan_origin.translated(
- -delta.x(),
- -delta.y());
- relayout();
- }
- void ViewWidget::mousewheel_event(GUI::MouseEvent& event)
- {
- int new_scale = m_scale - event.wheel_delta() * 10;
- if (new_scale < 10)
- new_scale = 10;
- if (new_scale > 1000)
- new_scale = 1000;
- if (new_scale == m_scale) {
- return;
- }
- auto old_scale_factor = (float)m_scale / 100.0f;
- auto new_scale_factor = (float)new_scale / 100.0f;
- // focus_point is the window position the cursor is pointing to.
- // The pixel (in image space) the cursor points to is located at
- // (m_pan_origin + focus_point) / scale_factor.
- // We want the image after scaling to be panned in such a way that the cursor
- // will still point to the same image pixel. Basically, we need to solve
- // (m_pan_origin + focus_point) / old_scale_factor = (new_m_pan_origin + focus_point) / new_scale_factor.
- Gfx::FloatPoint focus_point {
- event.x() - width() / 2.0f,
- event.y() - height() / 2.0f
- };
- // A little algebra shows that new m_pan_origin equals to:
- m_pan_origin = (m_pan_origin + focus_point) * (new_scale_factor / old_scale_factor) - focus_point;
- set_scale(new_scale);
- }
- void ViewWidget::load_from_file(const String& path)
- {
- 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();
- // Spawn a new ImageDecoder service process and connect to it.
- auto client = ImageDecoderClient::Client::construct();
- auto decoded_image_or_error = client->decode_image(mapped_file.bytes());
- if (!decoded_image_or_error.has_value()) {
- show_error();
- return;
- }
- m_decoded_image = decoded_image_or_error.release_value();
- m_bitmap = m_decoded_image->frames[0].bitmap;
- if (on_image_change)
- on_image_change(m_bitmap);
- if (m_decoded_image->is_animated && m_decoded_image->frames.size() > 1) {
- const auto& first_frame = m_decoded_image->frames[0];
- m_timer->set_interval(first_frame.duration);
- m_timer->on_timeout = [this] { animate(); };
- m_timer->start();
- } else {
- m_timer->stop();
- }
- m_path = Core::File::real_path_for(path);
- m_scale = -1;
- reset_view();
- }
- void ViewWidget::drop_event(GUI::DropEvent& event)
- {
- event.accept();
- if (on_drop)
- on_drop(event);
- }
- void ViewWidget::resize_window()
- {
- if (window()->is_fullscreen() || window()->is_maximized())
- return;
- auto absolute_bitmap_rect = m_bitmap_rect;
- absolute_bitmap_rect.translate_by(window()->rect().top_left());
- if (window()->rect().contains(absolute_bitmap_rect))
- return;
- if (!m_bitmap)
- return;
- auto new_size = m_bitmap->size();
- if (new_size.width() < 300)
- new_size.set_width(300);
- if (new_size.height() < 200)
- new_size.set_height(200);
- new_size.set_height(new_size.height() + m_toolbar_height);
- window()->resize(new_size);
- }
- void ViewWidget::reset_view()
- {
- m_pan_origin = { 0, 0 };
- set_scale(100);
- }
- void ViewWidget::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 ViewWidget::animate()
- {
- if (!m_decoded_image.has_value())
- return;
- m_current_frame_index = (m_current_frame_index + 1) % m_decoded_image->frames.size();
- const auto& current_frame = m_decoded_image->frames[m_current_frame_index];
- set_bitmap(current_frame.bitmap);
- if ((int)current_frame.duration != m_timer->interval()) {
- m_timer->restart(current_frame.duration);
- }
- if (m_current_frame_index == m_decoded_image->frames.size() - 1) {
- ++m_loops_completed;
- if (m_loops_completed > 0 && m_loops_completed == m_decoded_image->loop_count) {
- m_timer->stop();
- }
- }
- }
- }
|