rawTherapee/rtgui/crophandler.cc

662 lines
17 KiB
C++

/*
* This file is part of RawTherapee.
*
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "crophandler.h"
#undef THREAD_PRIORITY_NORMAL
#include <cstring>
#include "guiutils.h"
#include "cropwindow.h"
#include "imagearea.h"
#include "../rtengine/dcrop.h"
#include "../rtengine/refreshmap.h"
#include "../rtengine/rt_math.h"
using namespace rtengine;
CropHandler::CropHandler() :
zoom(100),
ww(0),
wh(0),
cax(-1),
cay(-1),
cx(0),
cy(0),
cw(0),
ch(0),
cropX(0),
cropY(0),
cropW(0),
cropH(0),
enabled(false),
cropimg_width(0),
cropimg_height(0),
cix(0),
ciy(0),
ciw(0),
cih(0),
cis(1),
isLowUpdatePriority(false),
ipc(nullptr),
crop(nullptr),
displayHandler(nullptr),
redraw_needed(false),
initial(false)
{
}
CropHandler::~CropHandler ()
{
idle_register.destroy();
if (ipc) {
ipc->delSizeListener (this);
}
setEnabled (false);
if (crop) {
//crop->destroy ();
delete crop; // will do the same than destroy, plus delete the object
crop = nullptr;
}
}
void CropHandler::setEditSubscriber (EditSubscriber* newSubscriber)
{
(static_cast<rtengine::Crop *>(crop))->setEditSubscriber(newSubscriber);
}
void CropHandler::newImage (StagedImageProcessor* ipc_, bool isDetailWindow)
{
ipc = ipc_;
cx = 0;
cy = 0;
if (!ipc) {
return;
}
EditDataProvider *editDataProvider = nullptr;
CropWindow *cropWin = displayHandler ? static_cast<CropWindow*>(displayHandler) : nullptr;
if (cropWin) {
editDataProvider = cropWin->getImageArea();
}
crop = ipc->createCrop (editDataProvider, isDetailWindow);
ipc->setSizeListener (this);
crop->setListener (enabled ? this : nullptr);
initial = true;
}
void CropHandler::sizeChanged (int x, int y, int ow, int oh) // the ipc notifies it to keep track size changes like rotation
{
compDim ();
// this should be put into an idle source!!!
/* if (listener)
listener->cropWindowChanged ();
*/
}
bool CropHandler::isFullDisplay ()
{
int w, h;
getFullImageSize(w, h);
if (!w) {
return false;
}
return cropW == w && cropH == h;
}
double CropHandler::getFitCropZoom ()
{
double z1 = (double) wh / cropParams.h;
double z2 = (double) ww / cropParams.w;
return z1 < z2 ? z1 : z2;
}
double CropHandler::getFitZoom ()
{
if (ipc) {
double z1 = (double) wh / ipc->getFullHeight ();
double z2 = (double) ww / ipc->getFullWidth ();
return z1 < z2 ? z1 : z2;
} else {
return 1.0;
}
}
void CropHandler::setZoom (int z, int centerx, int centery)
{
assert (ipc);
int oldZoom = zoom;
float oldScale = zoom >= 1000 ? float(zoom / 1000) : 10.f / float(zoom);
float newScale = z >= 1000 ? float(z / 1000) : 10.f / float(z);
int oldcax = cax;
int oldcay = cay;
if (centerx == -1) {
cax = ipc->getFullWidth () / 2;
} else {
float distToAnchor = float(cax - centerx);
distToAnchor = distToAnchor / newScale * oldScale;
cax = centerx + int(distToAnchor);
}
if (centery == -1) {
cay = ipc->getFullHeight () / 2;
} else {
float distToAnchor = float(cay - centery);
distToAnchor = distToAnchor / newScale * oldScale;
cay = centery + int(distToAnchor);
}
// maybe demosaic etc. if we cross the border to >100%
bool needsFullRefresh = (z >= 1000 && zoom < 1000);
zoom = z;
if (zoom >= 1000) {
cw = ww * 1000 / zoom;
ch = wh * 1000 / zoom;
} else {
cw = ww * (zoom / 10);
ch = wh * (zoom / 10);
}
cx = cax - cw / 2;
cy = cay - ch / 2;
int oldCropX = cropX;
int oldCropY = cropY;
int oldCropW = cropW;
int oldCropH = cropH;
compDim ();
if (enabled && (oldZoom != zoom || oldcax != cax || oldcay != cay || oldCropX != cropX || oldCropY != cropY || oldCropW != cropW || oldCropH != cropH)) {
if (needsFullRefresh && !ipc->getHighQualComputed()) {
cropPixbuf.clear ();
ipc->startProcessing(M_HIGHQUAL);
ipc->setHighQualComputed();
} else {
update ();
}
}
}
float CropHandler::getZoomFactor ()
{
if (zoom >= 1000) {
return zoom / 1000;
} else {
return 10.f / (float)zoom;
}
}
void CropHandler::setWSize (int w, int h)
{
ww = w;
wh = h;
if (zoom >= 1000) {
cw = ww * 1000 / zoom;
ch = wh * 1000 / zoom;
} else {
cw = ww * (zoom / 10);
ch = wh * (zoom / 10);
}
compDim ();
if (enabled) {
update ();
}
}
void CropHandler::getWSize (int& w, int &h)
{
w = ww;
h = wh;
}
void CropHandler::getAnchorPosition (int& x, int& y)
{
x = cax;
y = cay;
}
void CropHandler::setAnchorPosition (int x, int y, bool update_)
{
cax = x;
cay = y;
compDim ();
if (enabled && update_) {
update ();
}
}
void CropHandler::moveAnchor (int deltaX, int deltaY, bool update_)
{
cax += deltaX;
cay += deltaY;
compDim ();
if (enabled && update_) {
update ();
}
}
void CropHandler::centerAnchor (bool update_)
{
assert (ipc);
// Computes the crop's size and position given the anchor's position and display size
cax = ipc->getFullWidth() / 2;
cay = ipc->getFullHeight() / 2;
compDim ();
if (enabled && update_) {
update ();
}
}
void CropHandler::getPosition (int& x, int& y)
{
x = cropX;
y = cropY;
}
void CropHandler::setDetailedCrop (IImage8* im, IImage8* imtrue, rtengine::procparams::ColorManagementParams cmp,
rtengine::procparams::CropParams cp, int ax, int ay, int aw, int ah, int askip)
{
if (!enabled) {
return;
}
cimg.lock ();
cropParams = cp;
colorParams = cmp;
if (!cropimg.empty()) {
cropimg.clear();
}
if (!cropimgtrue.empty()) {
cropimgtrue.clear();
}
if (ax == cropX && ay == cropY && aw == cropW && ah == cropH && askip == (zoom >= 1000 ? 1 : zoom / 10)) {
cropimg_width = im->getWidth ();
cropimg_height = im->getHeight ();
const std::size_t cropimg_size = 3 * cropimg_width * cropimg_height;
cropimg.assign(im->getData(), im->getData() + cropimg_size);
cropimgtrue.assign(imtrue->getData(), imtrue->getData() + cropimg_size);
cix = ax;
ciy = ay;
ciw = aw;
cih = ah;
cis = askip;
bool expected = false;
if (redraw_needed.compare_exchange_strong(expected, true)) {
const auto func = [](gpointer data) -> gboolean {
CropHandler* const self = static_cast<CropHandler*>(data);
self->cimg.lock ();
if (self->redraw_needed.exchange(false)) {
self->cropPixbuf.clear ();
if (!self->enabled) {
self->cropimg.clear();
self->cropimgtrue.clear();
self->cimg.unlock ();
return FALSE;
}
if (!self->cropimg.empty()) {
if (self->cix == self->cropX && self->ciy == self->cropY && self->ciw == self->cropW && self->cih == self->cropH && self->cis == (self->zoom >= 1000 ? 1 : self->zoom / 10)) {
// calculate final image size
float czoom = self->zoom >= 1000 ?
self->zoom / 1000.f :
float((self->zoom/10) * 10) / float(self->zoom);
int imw = self->cropimg_width * czoom;
int imh = self->cropimg_height * czoom;
if (imw > self->ww) {
imw = self->ww;
}
if (imh > self->wh) {
imh = self->wh;
}
Glib::RefPtr<Gdk::Pixbuf> tmpPixbuf = Gdk::Pixbuf::create_from_data (self->cropimg.data(), Gdk::COLORSPACE_RGB, false, 8, self->cropimg_width, self->cropimg_height, 3 * self->cropimg_width);
self->cropPixbuf = Gdk::Pixbuf::create (Gdk::COLORSPACE_RGB, false, 8, imw, imh);
tmpPixbuf->scale (self->cropPixbuf, 0, 0, imw, imh, 0, 0, czoom, czoom, Gdk::INTERP_TILES);
tmpPixbuf.clear ();
Glib::RefPtr<Gdk::Pixbuf> tmpPixbuftrue = Gdk::Pixbuf::create_from_data (self->cropimgtrue.data(), Gdk::COLORSPACE_RGB, false, 8, self->cropimg_width, self->cropimg_height, 3 * self->cropimg_width);
self->cropPixbuftrue = Gdk::Pixbuf::create (Gdk::COLORSPACE_RGB, false, 8, imw, imh);
tmpPixbuftrue->scale (self->cropPixbuftrue, 0, 0, imw, imh, 0, 0, czoom, czoom, Gdk::INTERP_TILES);
tmpPixbuftrue.clear ();
}
self->cropimg.clear();
self->cropimgtrue.clear();
}
self->cimg.unlock ();
if (self->displayHandler) {
self->displayHandler->cropImageUpdated ();
if (self->initial.exchange(false)) {
self->displayHandler->initialImageArrived ();
}
}
} else {
self->cimg.unlock();
}
return FALSE;
};
idle_register.add(func, this/*, G_PRIORITY_HIGH_IDLE*/);
}
}
cimg.unlock ();
}
bool CropHandler::getWindow (int& cwx, int& cwy, int& cww, int& cwh, int& cskip)
{
cwx = cropX;
cwy = cropY;
cww = cropW;
cwh = cropH;
// hack: if called before first size allocation the size will be 0
if (cww == 0) {
cww = 10;
}
if (cwh == 0) {
cwh = 32;
}
cskip = zoom >= 1000 ? 1 : zoom/10;
return true;
}
void CropHandler::update ()
{
if (crop && enabled) {
// crop->setWindow (cropX, cropY, cropW, cropH, zoom>=1000 ? 1 : zoom); --> we use the "getWindow" hook instead of setting the size before
crop->setListener (this);
cropPixbuf.clear ();
// To save threads, try to mark "needUpdate" without a thread first
if (crop->tryUpdate()) {
if (isLowUpdatePriority) {
Glib::Thread::create(sigc::mem_fun(*crop, &DetailedCrop::fullUpdate), 0, false, true, Glib::THREAD_PRIORITY_LOW);
} else {
Glib::Thread::create(sigc::mem_fun(*crop, &DetailedCrop::fullUpdate), false );
}
}
}
}
void CropHandler::setEnabled (bool e)
{
enabled = e;
if (!enabled) {
if (crop) {
crop->setListener (nullptr);
}
cimg.lock();
cropimg.clear();
cropimgtrue.clear();
cropPixbuf.clear();
cimg.unlock();
} else {
update ();
}
}
bool CropHandler::getEnabled ()
{
return enabled;
}
void CropHandler::colorPick (const rtengine::Coord &pickerPos, float &r, float &g, float &b, float &rpreview, float &gpreview, float &bpreview, LockableColorPicker::Size size)
{
if (!cropPixbuf || !cropPixbuftrue) {
r = g = b = 0.f;
rpreview = gpreview = bpreview = 0.f;
return;
}
int xSize = (int)size;
int ySize = (int)size;
int pixbufW = cropPixbuftrue->get_width();
int pixbufH = cropPixbuftrue->get_height();
rtengine::Coord topLeftPos(pickerPos.x - xSize/2, pickerPos.y - ySize/2);
if (topLeftPos.x > pixbufW || topLeftPos.y > pixbufH || topLeftPos.x + xSize < 0 || topLeftPos.y + ySize < 0) {
return;
}
// Store the position of the center of the picker
int radius = (int)size / 2;
// X/Width clip
if (topLeftPos.x < 0) {
xSize += topLeftPos.x;
topLeftPos.x = 0;
}
if (topLeftPos.x + xSize > pixbufW) {
xSize = pixbufW - topLeftPos.x;
}
// Y/Height clip
if (topLeftPos.y < 0) {
ySize += topLeftPos.y;
topLeftPos.y = 0;
}
if (topLeftPos.y + ySize > pixbufH) {
ySize = pixbufH - topLeftPos.y;
}
// Accumulating the data
std::uint32_t r2=0, g2=0, b2=0;
std::uint32_t count = 0;
const guint8* data = cropPixbuftrue->get_pixels();
for (int j = topLeftPos.y ; j < topLeftPos.y + ySize ; ++j) {
const guint8* data2 = data + cropPixbuftrue->get_rowstride()*j;
for (int i = topLeftPos.x ; i < topLeftPos.x + xSize ; ++i) {
const guint8* data3 = data2 + i*3;
rtengine::Coord currPos(i, j);
rtengine::Coord delta = pickerPos - currPos;
rtengine::PolarCoord p(delta);
if (p.radius <= radius) {
r2 += *data3;
g2 += *(data3+1);
b2 += *(data3+2);
++count;
}
}
}
count = std::max(1u, count);
// Averaging
r = (float)r2 / (float)count / 255.f;
g = (float)g2 / (float)count / 255.f;
b = (float)b2 / (float)count / 255.f;
// Accumulating the data
r2=0, g2=0, b2=0;
count = 0;
data = cropPixbuf->get_pixels();
for (int j = topLeftPos.y ; j < topLeftPos.y + ySize ; ++j) {
const guint8* data2 = data + cropPixbuf->get_rowstride()*j;
for (int i = topLeftPos.x ; i < topLeftPos.x + xSize ; ++i) {
const guint8* data3 = data2 + i*3;
rtengine::Coord currPos(i, j);
rtengine::Coord delta = pickerPos - currPos;
rtengine::PolarCoord p(delta);
if (p.radius <= radius) {
r2 += *data3;
g2 += *(data3+1);
b2 += *(data3+2);
++count;
}
}
}
count = std::max(1u, count);
// Averaging
rpreview = (float)r2 / (float)count / 255.f;
gpreview = (float)g2 / (float)count / 255.f;
bpreview = (float)b2 / (float)count / 255.f;
}
void CropHandler::getSize (int& w, int& h)
{
w = cropW;
h = cropH;
}
void CropHandler::getFullImageSize (int& w, int& h)
{
if (ipc) {
w = ipc->getFullWidth ();
h = ipc->getFullHeight ();
} else {
w = h = 0;
}
}
void CropHandler::compDim ()
{
assert (ipc && displayHandler);
// Computes the crop's size and position given the anchor's position and display size
int fullW = ipc->getFullWidth();
int fullH = ipc->getFullHeight();
int imgX = -1, imgY = -1;
//int scaledFullW, scaledFullH;
int scaledCAX, scaledCAY;
int wwImgSpace;
int whImgSpace;
cax = rtengine::LIM(cax, 0, fullW-1);
cay = rtengine::LIM(cay, 0, fullH-1);
if (zoom >= 1000) {
wwImgSpace = int(float(ww) / float(zoom/1000) + 0.5f);
whImgSpace = int(float(wh) / float(zoom/1000) + 0.5f);
//scaledFullW = fullW * (zoom/1000);
//scaledFullH = fullH * (zoom/1000);
scaledCAX = cax * (zoom/1000);
scaledCAY = cay * (zoom/1000);
} else {
wwImgSpace = int(float(ww) * (float(zoom)/10.f) + 0.5f);
whImgSpace = int(float(wh) * (float(zoom)/10.f) + 0.5f);
//scaledFullW = fullW / zoom;
//scaledFullH = fullH / zoom;
scaledCAX = int(float(cax) / (float(zoom)/10.f));
scaledCAY = int(float(cay) / (float(zoom)/10.f));
}
imgX = ww / 2 - scaledCAX;
if (imgX < 0) {
imgX = 0;
}
imgY = wh / 2 - scaledCAY;
if (imgY < 0) {
imgY = 0;
}
cropX = cax - (wwImgSpace/2);
cropY = cay - (whImgSpace/2);
cropW = wwImgSpace;
cropH = whImgSpace;
if (cropX + cropW > fullW) {
cropW = fullW - cropX;
}
if (cropY + cropH > fullH) {
cropH = fullH - cropY;
}
if (cropX < 0) {
cropW += cropX;
cropX = 0;
}
if (cropY < 0) {
cropH += cropY;
cropY = 0;
}
// Should be good already, but this will correct eventual rounding error
if (cropW > fullW) {
cropW = fullW;
}
if (cropH > fullH) {
cropH = fullH;
}
displayHandler->setDisplayPosition(imgX, imgY);
}