/* * This file is part of Chalk * * Copyright (c) 2005 Boudewijn Rempt * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Ported from the CImg Gimp plugin by Victor Stinner and uses CImg by David Tschumperlé. * See: http://www.girouette-stinner.com/castor/gimp.html?girouette=ad761bc2f4dcfda1cb44c587da17f86c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_cimgconfig_widget.h" #include "kis_cimg_filter.h" #include "CImg.h" using namespace cimg_library; typedef unsigned char uchar; KisCImgFilterConfiguration::KisCImgFilterConfiguration() : KisFilterConfiguration("cimg", 1) { nb_iter = 1; dt = 20.0; sigma = 1.4; dlength = 0.8; dtheta = 45.0; onormalize = false; power1 = 0.1; power2 = 0.9; gauss_prec = 3.0; linear = true; } void KisCImgFilterConfiguration::fromXML(const TQString & s) { KisFilterConfiguration::fromXML( s ); nb_iter = getInt("nb_iter", 1); dt = getDouble("dt", 20.0); sigma = getDouble("sigma", 1.4); dlength = getDouble("dlength", 0.8); dtheta = getDouble("dtheta", 45.0); onormalize = getBool("onormalize", false); power1 = getDouble("power1", 0.1); power2 = getDouble("power2", 0.9); gauss_prec = getDouble("gauss_pref", 3.0); linear = getBool("linear", true); } TQString KisCImgFilterConfiguration::toString() { m_properties.clear(); setProperty("nb_iter", nb_iter); setProperty("dt", dt); setProperty("sigma", sigma); setProperty("dlength", dlength); setProperty("dtheta", dtheta); setProperty("onormalize", onormalize); setProperty("power1", power1); setProperty("power2", power2); setProperty("gauss_prec", gauss_prec); setProperty("linear", linear); return KisFilterConfiguration::toString(); } KisCImgFilter::KisCImgFilter() : KisFilter(id(), "enhance", i18n("&CImg Image Restoration...")), eigen(CImg<>(2,1), CImg<>(2,2)) { restore = true; inpaint = false; resize = false; visuflow = NULL; /* restore */ nb_iter = 1; dt = 20.0f; sigma = 0.8f; dlength = 0.8; dtheta = 45.0; onormalize = false; power1 = 0.5; power2 = 0.9; /* inpainting * nb_iter = 100; dt = 50.0f; sigma = 2.0; power1 = 0.1; power2 = 100; dlength = 0.8; dtheta = 45.0; */ /* resize * nb_iter = 1; dt = 30.0f; sigma = 2.0; dlength = 0.8; dtheta = 45.0; power1 = 0.01; power2 = 100.0; */ /* visualflow * nb_iter = 1; dt = 30.0f; dlength = 0.5; dtheta = 20.0; onormalize = false; */ gauss_prec = 3.0f; linear = true; } void KisCImgFilter::process(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisFilterConfiguration* configuration, const TQRect& rect) { Q_UNUSED(dst); TQ_INT32 width = rect.width(); TQ_INT32 height = rect.height(); // Copy the src data into a CImg type image with three channels and no alpha. // XXX: This means that a CImg is always rgba; find the quickest way to get 8-bit rgb from any colorspace & find a way // to warn in the gui of loss of precision. XXX: Add this to the ColorSpaceAPI doc. img = CImg<>(width, height, 1, 3); KisColorSpace * cs = src->colorSpace(); KisColorSpace* rgb16CS = KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA16"),""); KisPaintDeviceSP srcRGB16; if(rgb16CS) { srcRGB16 = new KisPaintDevice(*src.data()); srcRGB16->convertTo(rgb16CS); KisRectIteratorPixel it = srcRGB16->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), false); while (!it.isDone()) { TQ_UINT16* data = reinterpret_cast(it.rawData()); TQ_INT32 x = it.x() - rect.x(); TQ_INT32 y = it.y() - rect.y(); img(x, y, 0) = data[0]; img(x, y, 1) = data[1]; img(x, y, 2) = data[2]; ++it; } } else { kdDebug() << "The RGB16 colorspace is not available, will work in 8bit." << endl; KisRectIteratorPixel it = src->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), false); while (!it.isDone()) { TQColor color; cs->toTQColor(it.rawData(), &color); TQ_INT32 x = it.x() - rect.x(); TQ_INT32 y = it.y() - rect.y(); img(x, y, 0) = color.red(); img(x, y, 1) = color.green(); img(x, y, 2) = color.blue(); ++it; } } // Copy the config data into local variables for easy cut & pasting from the original plugin KisCImgFilterConfiguration * cfg = (KisCImgFilterConfiguration*)configuration; nb_iter = cfg->nb_iter; dt = cfg->dt; dlength = cfg->dlength; dtheta = cfg->dtheta; sigma = cfg->sigma; power1 = cfg->power1; power2 = cfg->power2; gauss_prec = cfg->gauss_prec; onormalize = cfg->onormalize; linear = cfg->linear; if (process() && !cancelRequested()) { if(rgb16CS) { { KisRectIteratorPixel it = srcRGB16->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), true); while (!it.isDone()) { TQ_INT32 x = it.x() - rect.x(); TQ_INT32 y = it.y() - rect.y(); TQ_UINT16* data = reinterpret_cast(it.rawData()); data[0] = img(x, y, 0) ; data[1] = img(x, y, 1) ; data[2] = img(x, y, 2) ; ++it; } } srcRGB16->convertTo(cs); KisPainter p(dst); p.bitBlt(rect.x(), rect.y(), COMPOSITE_OVER, srcRGB16, rect.x(), rect.y(), rect.width(), rect.height() ); } else { KisRectIteratorPixel it = dst->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), true); while (!it.isDone()) { if (it.isSelected()) { TQ_INT32 x = it.x() - rect.x(); TQ_INT32 y = it.y() - rect.y(); TQColor color((int)img(x, y, 0), (int)img(x, y, 1), (int)img(x, y, 2)); cs->fromTQColor(color, it.rawData()); } ++it; } } } else { // Everything went wrong; notify user and restore old state } } //---------------------------------------------------------------------------- // Cut & Pasted code starts here.... //---------------------------------------------------------------------------- void get_geom(const char *geom, int &geom_w, int &geom_h) { char tmp[16]; std::sscanf(geom,"%d%7[^0-9]%d%7[^0-9]",&geom_w,tmp,&geom_h,tmp+1); if (tmp[0]=='%') geom_w=-geom_w; if (tmp[1]=='%') geom_h=-geom_h; } //---------------------------------------------------------------------------- void KisCImgFilter::cleanup() { img0 = flow = G = dest = sum= W = CImg<>(); mask = CImg (); } //---------------------------------------------------------------------------- bool KisCImgFilter::prepare() { if (!restore && !inpaint && !resize && !visuflow) { // XXX: Do KDE messagebox // g_message ("You must specify one of the restore, inpaint, resize or flow mode !"); return false; } // Init algorithm parameters //--------------------------- if (restore) if (!prepare_restore()) return false; if (inpaint) if (!prepare_inpaint()) return false; if (resize) if (!prepare_resize()) return false; if (visuflow) if (!prepare_visuflow()) return false; if (!check_args()) return false; // Init images //------------ dest = CImg<>(img.width,img.height,1,img.dim); sum = CImg<>(img.width,img.height,1); W = CImg<>(img.width,img.height,1,2); return true; } //---------------------------------------------------------------------------- bool KisCImgFilter::check_args() { if (power2 < power1) { // XXX: Do KDE messagebox // g_message ("Error : p2(img.width,img.height,1,3); return true; } //---------------------------------------------------------------------------- bool KisCImgFilter::prepare_inpaint() { const char *file_m = NULL; //cimg_option("-m",(const char*)NULL,"Input inpainting mask"); if (!file_m) { // XXX: Do KDE messagebox // g_message ("You need to specify an inpainting mask (option '-m') !"); return false; } const unsigned int dilate = 0; //cimg_option("-dilate",0,"Inpainting mask dilatation"); const unsigned int ip_init = 3; //cimg_option("-init",3,"Inpainting init (0=black, 1=white, 2=noise, 3=unchanged, 4=interpol)"); if (cimg::strncasecmp("block",file_m,5)) mask = CImg(file_m); else { int l=16; std::sscanf(file_m,"block%d",&l); mask = CImg(img.width/l,img.height/l); cimg_mapXY(mask,x,y) mask(x,y)=(x+y)%2; } mask.resize(img.width,img.height,1,1); if (dilate) mask.dilate(dilate); switch (ip_init) { case 0 : { cimg_mapXYV(img,x,y,k) if (mask(x,y)) img(x,y,k) = 0; } break; case 1 : { cimg_mapXYV(img,x,y,k) if (mask(x,y)) img(x,y,k) = 255; } break; case 2 : { cimg_mapXYV(img,x,y,k) if (mask(x,y)) img(x,y,k) = (float)(255*cimg::rand()); } break; case 3 : break; case 4 : { CImg tmask(mask),ntmask(tmask); CImg_3x3(M,uchar); CImg_3x3(I,float); while (CImgStats(ntmask,false).max>0) { cimg_map3x3(tmask,x,y,0,0,M) if (Mcc && (!Mpc || !Mnc || !Mcp || !Mcn)) { const float ccp = Mcp?0.0f:1.0f, cpc = Mpc?0.0f:1.0f, cnc = Mnc?0.0f:1.0f, ccn = Mcn?0.0f:1.0f, csum = ccp + cpc + cnc + ccn; cimg_mapV(img,k) { cimg_get3x3(img,x,y,0,k,I); img(x,y,k) = (ccp*Icp + cpc*Ipc + cnc*Inc + ccn*Icn)/csum; } ntmask(x,y) = 0; } tmask = ntmask; } } break; default: break; } img0=img; G = CImg<>(img.width,img.height,1,3,0); CImg_3x3(g,uchar); CImg_3x3(I,float); cimg_map3x3(mask,x,y,0,0,g) if (!gcc && !(gnc-gcc) && !(gcc-gpc) && !(gcn-gcc) && !(gcc-gcp)) cimg_mapV(img,k) { cimg_get3x3(img,x,y,0,k,I); const float ix = 0.5f*(Inc-Ipc), iy = 0.5f*(Icn-Icp); G(x,y,0)+= ix*ix; G(x,y,1)+= ix*iy; G(x,y,2)+= iy*iy; } G.blur(sigma); { cimg_mapXY(G,x,y) { G.get_tensor(x,y).symeigen(eigen(0),eigen(1)); const float l1 = eigen(0)[0], l2 = eigen(0)[1], u = eigen(1)[0], v = eigen(1)[1], ng = (float)std::sqrt(l1+l2), n1 = (float)(1.0/std::pow(1+ng,power1)), n2 = (float)(1.0/std::pow(1+ng,power2)), sr1 = (float)std::sqrt(n1), sr2 = (float)std::sqrt(n2); G(x,y,0) = sr1*u*u + sr2*v*v; G(x,y,1) = u*v*(sr1-sr2); G(x,y,2) = sr1*v*v + sr2*u*u; } } return true; } //---------------------------------------------------------------------------- bool KisCImgFilter::prepare_resize() { const char *geom = NULL; //cimg_option("-g",(const char*)NULL,"Output image geometry"); const bool anchor = true; //cimg_option("-anchor",true,"Anchor original pixels"); if (!geom) throw CImgArgumentException("You need to specify an output geomety (option -g)"); int w,h; get_geom(geom,w,h); mask = CImg(img.width,img.height,1,1,255); if (!anchor) mask.resize(w,h,1,1,1); else mask = ~mask.resize(w,h,1,1,4); img0 = img.get_resize(w,h,1,-100,1); img.resize(w,h,1,-100,3); G = CImg<>(img.width,img.height,1,3); return true; } //---------------------------------------------------------------------------- bool KisCImgFilter::prepare_visuflow() { const char *geom = "100%x100%"; //cimg_option("-g","100%x100%","Output geometry"); //const char *file_i = (const char *)NULL; //cimg_option("-i",(const char*)NULL,"Input init image"); const bool normalize = false; //cimg_option("-norm",false,"Normalize input flow"); int w,h; get_geom(geom,w,h); if (!cimg::strcasecmp(visuflow,"circle")) { // Create a circular vector flow flow = CImg<>(400,400,1,2); cimg_mapXY(flow,x,y) { const float ang = (float)(std::atan2(y-0.5*flow.dimy(),x-0.5*flow.dimx())); flow(x,y,0) = -(float)std::sin(ang); flow(x,y,1) = (float)std::cos(ang); } } if (!cimg::strcasecmp(visuflow,"radial")) { // Create a radial vector flow flow = CImg<>(400,400,1,2); cimg_mapXY(flow,x,y) { const float ang = (float)(std::atan2(y-0.5*flow.dimy(),x-0.5*flow.dimx())); flow(x,y,0) = (float)std::cos(ang); flow(x,y,1) = (float)std::sin(ang); } } if (!flow.data) flow = CImg<>(visuflow); flow.resize(w,h,1,2,3); if (normalize) flow.orientation_pointwise(); /* if (file_i) img = CImg<>(file_i); else img = CImg<>(flow.width,flow.height,1,1,0).noise(100,2); */ img0=img; img0.fill(0); float color[3]={255,255,255}; img0.draw_quiver(flow,color,15,-10); G = CImg<>(img.width,img.height,1,3); return true; } //---------------------------------------------------------------------------- void KisCImgFilter::compute_smoothed_tensor() { if (visuflow || inpaint) return; CImg_3x3(I,float); G.fill(0); cimg_mapV(img,k) cimg_map3x3(img,x,y,0,k,I) { const float ix = 0.5f*(Inc-Ipc), iy = 0.5f*(Icn-Icp); G(x,y,0)+= ix*ix; G(x,y,1)+= ix*iy; G(x,y,2)+= iy*iy; } G.blur(sigma); } //---------------------------------------------------------------------------- void KisCImgFilter::compute_normalized_tensor() { if (restore || resize) cimg_mapXY(G,x,y) { G.get_tensor(x,y).symeigen(eigen(0),eigen(1)); const float l1 = eigen(0)[0], l2 = eigen(0)[1], u = eigen(1)[0], v = eigen(1)[1], n1 = (float)(1.0/std::pow(1.0f+l1+l2,0.5f*power1)), n2 = (float)(1.0/std::pow(1.0f+l1+l2,0.5f*power2)); G(x,y,0) = n1*u*u + n2*v*v; G(x,y,1) = u*v*(n1-n2); G(x,y,2) = n1*v*v + n2*u*u; } if (visuflow) cimg_mapXY(G,x,y) { const float u = flow(x,y,0), v = flow(x,y,1), n = (float)std::pow(u*u+v*v,0.25f), nn = n < 1e-5 ? 1 : n; G(x,y,0) = u*u/nn; G(x,y,1) = u*v/nn; G(x,y,2) = v*v/nn; } const CImgStats stats(G,false); G /= cimg::max(std::fabs(stats.max), std::fabs(stats.min)); } //---------------------------------------------------------------------------- void KisCImgFilter::compute_W(float cost, float sint) { cimg_mapXY(W,x,y) { const float a = G(x,y,0), b = G(x,y,1), c = G(x,y,2), u = a*cost + b*sint, v = b*cost + c*sint; W(x,y,0) = u; W(x,y,1) = v; } } //---------------------------------------------------------------------------- void KisCImgFilter::compute_LIC_back_forward(int x, int y) { float l, X,Y, cu, cv, lsum=0; const float fsigma2 = 2*dt*(W(x,y,0)*W(x,y,0) + W(x,y,1)*W(x,y,1)), length = gauss_prec*(float)std::sqrt(fsigma2); if (linear) { // Integrate with linear interpolation cu = W(x,y,0); cv = W(x,y,1); X=(float)x; Y=(float)y; for (l=0; l=0 && Y>=0 && X<=W.dimx()-1 && Y<=W.dimy()-1; l+=dlength) { float u = (float)(W.linear_pix2d(X,Y,0)), v = (float)(W.linear_pix2d(X,Y,1)); const float coef = (float)std::exp(-l*l/fsigma2); if ((cu*u+cv*v)<0) { u=-u; v=-v; } cimg_mapV(dest,k) dest(x,y,k)+=(float)(coef*img.linear_pix2d(X,Y,k)); X+=dlength*u; Y+=dlength*v; cu=u; cv=v; lsum+=coef; } cu = W(x,y,0); cv = W(x,y,1); X=x-dlength*cu; Y=y-dlength*cv; for (l=dlength; l=0 && Y>=0 && X<=W.dimx()-1 && Y<=W.dimy()-1; l+=dlength) { float u = (float)(W.linear_pix2d(X,Y,0)), v = (float)(W.linear_pix2d(X,Y,1)); const float coef = (float)std::exp(-l*l/fsigma2); if ((cu*u+cv*v)<0) { u=-u; v=-v; } cimg_mapV(dest,k) dest(x,y,k)+=(float)(coef*img.linear_pix2d(X,Y,k)); X-=dlength*u; Y-=dlength*v; cu=u; cv=v; lsum+=coef; } } else { // Integrate with non linear interpolation cu = W(x,y,0); cv = W(x,y,1); X=(float)x; Y=(float)y; for (l=0; l=0 && Y>=0 && X<=W.dimx()-1 && Y<=W.dimy()-1; l+=dlength) { float u = W((int)X,(int)Y,0), v = W((int)X,(int)Y,1); const float coef = (float)std::exp(-l*l/fsigma2); if ((cu*u+cv*v)<0) { u=-u; v=-v; } cimg_mapV(dest,k) dest(x,y,k)+=(float)(coef*img.linear_pix2d(X,Y,k)); X+=dlength*u; Y+=dlength*v; cu=u; cv=v; lsum+=coef; } cu = W(x,y,0); cv = W(x,y,1); X=x-dlength*cu; Y=y-dlength*cv; for (l=dlength; l=0 && Y>=0 && X<=W.dimx()-1 && Y<=W.dimy()-1; l+=dlength) { float u = W((int)X,(int)Y,0), v = W((int)X,(int)Y,1); const float coef = (float)std::exp(-l*l/fsigma2); if ((cu*u+cv*v)<0) { u=-u; v=-v; } cimg_mapV(dest,k) dest(x,y,k)+=(float)(coef*img.linear_pix2d(X,Y,k)); X-=dlength*u; Y-=dlength*v; cu=u; cv=v; lsum+=coef; } } sum(x,y)+=lsum; } //---------------------------------------------------------------------------- void KisCImgFilter::compute_LIC(int &progressSteps) { dest.fill(0); sum.fill(0); for (float theta=(180%(int)dtheta)/2.0f; theta<180; theta+=dtheta) { const float rad = (float)(theta*cimg::PI/180.0), cost = (float)std::cos(rad), sint = (float)std::sin(rad); // Compute vector field w = sqrt(T)*a_alpha compute_W(cost, sint); // Compute the LIC along w in backward and forward directions cimg_mapXY(dest,x,y) { setProgress(progressSteps); progressSteps++; if (cancelRequested()) { return; } if (!mask.data || mask(x,y)) compute_LIC_back_forward(x,y); } } } //---------------------------------------------------------------------------- void KisCImgFilter::compute_average_LIC() { cimg_mapXY(dest,x,y) { if (sum(x,y)>0) cimg_mapV(dest,k) dest(x,y,k) /= sum(x,y); else cimg_mapV(dest,k) dest(x,y,k) = img(x,y,k); } } bool KisCImgFilter::process() { if (!prepare()) return false; setProgressTotalSteps(dest.width * dest.height * nb_iter * (int)ceil(180 / dtheta)); setProgressStage(i18n("Applying image restoration filter..."), 0); //------------------------------------- // Begin regularization PDE iterations //------------------------------------- int progressSteps = 0; for (unsigned int iter=0; iterconfig(); } } ColorSpaceIndependence KisCImgFilter::colorSpaceIndependence() { KisColorSpace* rgb16CS = KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA16"),""); if(rgb16CS) { return TO_RGBA16; } else { return TO_RGBA8; } }