/* * This file is part of RawTherapee. * * Copyright (c) 2004-2010 Gabor Horvath * * RawTherapee is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RawTherapee is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ #include "inspector.h" #include "guiutils.h" #include #include "cursormanager.h" #include "guiutils.h" #include "options.h" #include "pathutils.h" #include "rtscalable.h" #include "../rtengine/previewimage.h" #include "../rtengine/rt_math.h" InspectorBuffer::InspectorBuffer(const Glib::ustring &imagePath) : currTransform(0), fromRaw(false) { if (!imagePath.empty() && Glib::file_test(imagePath, Glib::FILE_TEST_EXISTS) && !Glib::file_test(imagePath, Glib::FILE_TEST_IS_DIR)) { imgPath = imagePath; // generate thumbnail image Glib::ustring ext = getExtension (imagePath); if (ext.empty()) { imgPath.clear(); return; } rtengine::PreviewImage pi(imagePath, ext, rtengine::PreviewImage::PIM_EmbeddedOrRaw); Cairo::RefPtr imageSurface = pi.getImage(); if (imageSurface) { imgBuffer.setSurface(imageSurface); fromRaw = true; } else { imgPath.clear(); } } } /* InspectorBuffer::~InspectorBuffer() { } */ //int InspectorBuffer::infoFromImage (const Glib::ustring& fname) //{ // // rtengine::FramesMetaData* idata = rtengine::FramesMetaData::fromFile (fname, nullptr, true); // // if (!idata) { // return 0; // } // // int deg = 0; // // if (idata->hasExif()) { // if (idata->getOrientation() == "Rotate 90 CW" ) { // deg = 90; // } else if (idata->getOrientation() == "Rotate 180" ) { // deg = 180; // } else if (idata->getOrientation() == "Rotate 270 CW") { // deg = 270; // } // } // // delete idata; // return deg; //} Inspector::Inspector () : currImage(nullptr), scaled(false), active(false) { set_name("Inspector"); window.set_visible(false); window.add_events(Gdk::KEY_PRESS_MASK); window.signal_key_release_event().connect(sigc::mem_fun(*this, &Inspector::on_key_release)); window.set_title("RawTherapee Inspector"); window.add(*this); window.show_all(); window.set_visible(false); active = true; // always track inspected thumbnails } Inspector::~Inspector() { deleteBuffers(); } void Inspector::showWindow(bool scaled) { this->scaled = scaled; window.fullscreen(); window.set_visible(true); } bool Inspector::on_key_release(GdkEventKey *event) { window.set_visible(false); return true; } bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) { Glib::RefPtr win = get_window(); if (!win) { return false; } if (!active) { active = true; } // cleanup the region if (currImage && currImage->imgBuffer.surfaceCreated()) { // this will eventually create/update the off-screen pixmap // compute the displayed area rtengine::Coord availableSize; rtengine::Coord topLeft; rtengine::Coord dest(0, 0); int deviceScale = get_scale_factor(); availableSize.x = win->get_width() * deviceScale; availableSize.y = win->get_height() * deviceScale; int imW = currImage->imgBuffer.getWidth(); int imH = currImage->imgBuffer.getHeight(); double scale = 1.0; if (scaled) { // reduce size of image to fit into window scale = rtengine::min(1.0, rtengine::min((double)availableSize.x/imW, (double)availableSize.y/imH)); availableSize.x /= scale; availableSize.y /= scale; } if (imW < availableSize.x) { // center the image in the available space along X topLeft.x = 0; dest.x = (availableSize.x - imW) / 2; } else { // partial image display // double clamp topLeft.x = center.x + availableSize.x / 2; topLeft.x = rtengine::min(topLeft.x, imW); topLeft.x -= availableSize.x; topLeft.x = rtengine::max(topLeft.x, 0); } if (imH < availableSize.y) { // center the image in the available space along Y topLeft.y = 0; dest.y = (availableSize.y - imH) / 2; } else { // partial image display // double clamp topLeft.y = center.y + availableSize.y / 2; topLeft.y = rtengine::min(topLeft.y, imH); topLeft.y -= availableSize.y; topLeft.y = rtengine::max(topLeft.y, 0); } //printf("center: %d, %d (img: %d, %d) (availableSize: %d, %d) (topLeft: %d, %d)\n", center.x, center.y, imW, imH, availableSize.x, availableSize.y, topLeft.x, topLeft.y); // define the destination area currImage->imgBuffer.setDrawRectangle(win, dest.x, dest.y, rtengine::min(availableSize.x - dest.x, imW), rtengine::min(availableSize.y - dest.y, imH), false); currImage->imgBuffer.setSrcOffset(topLeft.x, topLeft.y); if (!currImage->imgBuffer.surfaceCreated()) { return false; } // Draw! Gdk::RGBA c; Glib::RefPtr style = get_style_context(); // draw the background //style->render_background(cr, 0, 0, get_width(), get_height()); ///* --- old method (the new method does not seem to work) c = style->get_background_color (Gtk::STATE_FLAG_NORMAL); cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue()); cr->set_line_width (0); cr->rectangle (0, 0, availableSize.x, availableSize.y); cr->fill (); //*/ bool scaledImage = scaled && (imW > win->get_width() || imH > win->get_height()); if (deviceScale == 1 && !scaledImage) { // standard drawing currImage->imgBuffer.copySurface(win); } else { // consider device scale and image scale if (deviceScale > 1) { // use full device resolution and let it scale the image (macOS) cairo_surface_set_device_scale(cr->get_target()->cobj(), scale, scale); scaledImage = false; } Glib::RefPtr crop = Gdk::Pixbuf::create(currImage->imgBuffer.getSurface(), topLeft.x, topLeft.y, rtengine::min(imW, availableSize.x), rtengine::min(imH, availableSize.y)); if (!scaledImage) { Gdk::Cairo::set_source_pixbuf(cr, crop, dest.x, dest.y); } else { // assume that the device does not support scaling (Linux) crop = crop->scale_simple(imW*scale, imH*scale, Gdk::INTERP_BILINEAR); Gdk::Cairo::set_source_pixbuf(cr, crop, dest.x*scale, dest.y*scale); } cr->paint(); } /* --- not for separate window // draw the frame c = style->get_border_color (Gtk::STATE_FLAG_NORMAL); cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue()); cr->set_line_width (1); cr->rectangle (0.5, 0.5, availableSize.x - 1, availableSize.y - 1); cr->stroke (); */ } return true; } void Inspector::mouseMove (rtengine::Coord2D pos, int transform) { if (!active) { return; } if (currImage) { center.set(int(rtengine::LIM01(pos.x)*double(currImage->imgBuffer.getWidth())), int(rtengine::LIM01(pos.y)*double(currImage->imgBuffer.getHeight()))); } else { center.set(0, 0); } queue_draw(); } void Inspector::switchImage (const Glib::ustring &fullPath) { if (!active) { return; } if (delayconn.connected()) { delayconn.disconnect(); } next_image_path = fullPath; if (!options.inspectorDelay) { doSwitchImage(); } else { delayconn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &Inspector::doSwitchImage), options.inspectorDelay); } } bool Inspector::doSwitchImage() { Glib::ustring fullPath = next_image_path; // we first check the size of the list, it may have been changed in Preference if (images.size() > size_t(options.maxInspectorBuffers)) { // deleting the last entries for (size_t i = images.size() - 1; i > size_t(options.maxInspectorBuffers - 1); --i) { delete images.at(i); images.at(i) = nullptr; } // resizing down images.resize(options.maxInspectorBuffers); } if (fullPath.empty()) { currImage = nullptr; queue_draw(); } else { bool found = false; for (size_t i = 0; i < images.size(); ++i) { if (images.at(i) != nullptr && images.at(i)->imgPath == fullPath) { currImage = images.at(i); // rolling the list 1 step to the beginning for (size_t j = i; j < images.size() - 1; ++j) { images.at(j) = images.at(j + 1); } images.at(images.size() - 1) = currImage; // move the last used image to the tail found = true; break; } } if (!found) { if (images.size() == size_t(options.maxInspectorBuffers)) { // The list is full, delete the first entry delete images.at(0); images.erase(images.begin()); } // Loading a new image InspectorBuffer *iBuffer = new InspectorBuffer(fullPath); // and add it to the tail if (!iBuffer->imgPath.empty()) { images.push_back(iBuffer); currImage = images.at(images.size() - 1); } else { delete iBuffer; currImage = nullptr; } } } return true; } void Inspector::deleteBuffers () { for (size_t i = 0; i < images.size(); ++i) { if (images.at(i) != nullptr) { delete images.at(i); images.at(i) = nullptr; } } images.resize(0); currImage = nullptr; } void Inspector::flushBuffers () { if (!active) { return; } deleteBuffers(); } void Inspector::setActive(bool state) { if (!state) { flushBuffers(); } //active = state; } Gtk::SizeRequestMode Inspector::get_request_mode_vfunc () const { return Gtk::SIZE_REQUEST_CONSTANT_SIZE; } void Inspector::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const { minimum_height= 50 * RTScalable::getScale(); natural_height = 300 * RTScalable::getScale(); } void Inspector::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const { minimum_width = 50 * RTScalable::getScale(); natural_width = 200 * RTScalable::getScale(); } void Inspector::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const { get_preferred_height_vfunc(minimum_height, natural_height); } void Inspector::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const { get_preferred_width_vfunc (minimum_width, natural_width); }