Wed, 05 Aug 2009 15:02:31 +0100
PTdecode: add support for uncompressed data (NOTE: *NOT* supported by the PT-2450DX)
philpem@5 | 1 | /* |
philpem@5 | 2 | # |
philpem@5 | 3 | # File : curve_editor.cpp |
philpem@5 | 4 | # ( C++ source file ) |
philpem@5 | 5 | # |
philpem@5 | 6 | # Description : A simple user interface to construct 2D spline curves. |
philpem@5 | 7 | # This file is a part of the CImg Library project. |
philpem@5 | 8 | # ( http://cimg.sourceforge.net ) |
philpem@5 | 9 | # |
philpem@5 | 10 | # Copyright : David Tschumperle |
philpem@5 | 11 | # ( http://www.greyc.ensicaen.fr/~dtschump/ ) |
philpem@5 | 12 | # |
philpem@5 | 13 | # License : CeCILL v2.0 |
philpem@5 | 14 | # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html ) |
philpem@5 | 15 | # |
philpem@5 | 16 | # This software is governed by the CeCILL license under French law and |
philpem@5 | 17 | # abiding by the rules of distribution of free software. You can use, |
philpem@5 | 18 | # modify and/ or redistribute the software under the terms of the CeCILL |
philpem@5 | 19 | # license as circulated by CEA, CNRS and INRIA at the following URL |
philpem@5 | 20 | # "http://www.cecill.info". |
philpem@5 | 21 | # |
philpem@5 | 22 | # As a counterpart to the access to the source code and rights to copy, |
philpem@5 | 23 | # modify and redistribute granted by the license, users are provided only |
philpem@5 | 24 | # with a limited warranty and the software's author, the holder of the |
philpem@5 | 25 | # economic rights, and the successive licensors have only limited |
philpem@5 | 26 | # liability. |
philpem@5 | 27 | # |
philpem@5 | 28 | # In this respect, the user's attention is drawn to the risks associated |
philpem@5 | 29 | # with loading, using, modifying and/or developing or reproducing the |
philpem@5 | 30 | # software by the user in light of its specific status of free software, |
philpem@5 | 31 | # that may mean that it is complicated to manipulate, and that also |
philpem@5 | 32 | # therefore means that it is reserved for developers and experienced |
philpem@5 | 33 | # professionals having in-depth computer knowledge. Users are therefore |
philpem@5 | 34 | # encouraged to load and test the software's suitability as regards their |
philpem@5 | 35 | # requirements in conditions enabling the security of their systems and/or |
philpem@5 | 36 | # data to be ensured and, more generally, to use and operate it in the |
philpem@5 | 37 | # same conditions as regards security. |
philpem@5 | 38 | # |
philpem@5 | 39 | # The fact that you are presently reading this means that you have had |
philpem@5 | 40 | # knowledge of the CeCILL license and that you accept its terms. |
philpem@5 | 41 | # |
philpem@5 | 42 | */ |
philpem@5 | 43 | |
philpem@5 | 44 | #include "CImg.h" |
philpem@5 | 45 | using namespace cimg_library; |
philpem@5 | 46 | |
philpem@5 | 47 | // The lines below are necessary when using a non-standard compiler as visualcpp6. |
philpem@5 | 48 | #ifdef cimg_use_visualcpp6 |
philpem@5 | 49 | #define std |
philpem@5 | 50 | #endif |
philpem@5 | 51 | #ifdef min |
philpem@5 | 52 | #undef min |
philpem@5 | 53 | #undef max |
philpem@5 | 54 | #endif |
philpem@5 | 55 | |
philpem@5 | 56 | //--------------- |
philpem@5 | 57 | // Main procedure |
philpem@5 | 58 | //--------------- |
philpem@5 | 59 | int main(int argc, char **argv) { |
philpem@5 | 60 | |
philpem@5 | 61 | // Read command line parameters |
philpem@5 | 62 | //----------------------------- |
philpem@5 | 63 | cimg_usage("2D Spline Curve Editor"); |
philpem@5 | 64 | const char *file_i = cimg_option("-i",(char*)0,"Input image"); |
philpem@5 | 65 | const float contrast = cimg_option("-contrast",0.6f,"Image contrast"); |
philpem@5 | 66 | const char *file_ip = cimg_option("-ip",(char*)0,"Input control points"); |
philpem@5 | 67 | const char *file_oc = cimg_option("-oc",(char*)0,"Output curve points"); |
philpem@5 | 68 | const char *file_op = cimg_option("-op",(char*)0,"Output control points"); |
philpem@5 | 69 | const char *file_od = cimg_option("-od",(char*)0,"Output distance function"); |
philpem@5 | 70 | bool interp = cimg_option("-poly",true,"Use polynomial interpolation"); |
philpem@5 | 71 | bool closed = cimg_option("-closed",true,"Closed curve"); |
philpem@5 | 72 | bool show_tangents = cimg_option("-tangents",false,"Show tangents"); |
philpem@5 | 73 | bool show_points = cimg_option("-points",true,"Show control points"); |
philpem@5 | 74 | bool show_outline = cimg_option("-outline",true,"Show polygon outline"); |
philpem@5 | 75 | bool show_indices = cimg_option("-indices",true,"Show points indices"); |
philpem@5 | 76 | bool show_coordinates = cimg_option("-coords",false,"Show points coordinates"); |
philpem@5 | 77 | const float precision = cimg_option("-prec",0.05f,"Precision of curve discretization"); |
philpem@5 | 78 | |
philpem@5 | 79 | // Init image data |
philpem@5 | 80 | //----------------- |
philpem@5 | 81 | const unsigned char yellow[] = { 255,255,0 }, white[] = { 255,255,255 }, green[] = { 0,255,0 }, |
philpem@5 | 82 | red[] = { 255,0,50 }, purple[] = { 255,100,255 }, black[] = { 0,0,0 }; |
philpem@5 | 83 | CImg<unsigned char> img0, img, help_img; |
philpem@5 | 84 | if (file_i) { |
philpem@5 | 85 | std::fprintf(stderr,"\n - Load input image '%s' : ",cimg::basename(file_i)); |
philpem@5 | 86 | img0 = CImg<>(file_i).normalize(0,255.0f*contrast); |
philpem@5 | 87 | std::fprintf(stderr,"Size = %dx%dx%dx%d \n",img0.dimx(),img0.dimy(),img0.dimz(),img0.dimv()); |
philpem@5 | 88 | img0.resize(-100,-100,1,3); |
philpem@5 | 89 | } |
philpem@5 | 90 | else { |
philpem@5 | 91 | std::fprintf(stderr,"\n - No input image specified, use default 512x512 image.\n"); |
philpem@5 | 92 | img0.assign(512,512,1,3,0).draw_grid(32,32,0,0,false,false,green,0.4f,0xCCCCCCCC,0xCCCCCCCC); |
philpem@5 | 93 | } |
philpem@5 | 94 | |
philpem@5 | 95 | help_img.assign(270,160,1,3,0). |
philpem@5 | 96 | draw_text(5,5, |
philpem@5 | 97 | "------------------------------------------\n" |
philpem@5 | 98 | "2D Curve Editor\n" |
philpem@5 | 99 | "------------------------------------------\n" |
philpem@5 | 100 | "Left button : Create or move control point\n" |
philpem@5 | 101 | "Right button : Delete control point\n" |
philpem@5 | 102 | "Spacebar : Switch interpolation\n" |
philpem@5 | 103 | "Key 'C' : Switch open/closed mode\n" |
philpem@5 | 104 | "Key 'T' : Show/hide tangents\n" |
philpem@5 | 105 | "Key 'P' : Show/hide control points\n" |
philpem@5 | 106 | "Key 'O' : Show/hide polygon outline\n" |
philpem@5 | 107 | "Key 'N' : Show/hide points indices\n" |
philpem@5 | 108 | "Key 'X' : Show/hide points coordinates\n" |
philpem@5 | 109 | "Key 'H' : Show/hide this help\n" |
philpem@5 | 110 | "Key 'S' : Save control points\n" |
philpem@5 | 111 | "Key 'R' : Reset curve\n", |
philpem@5 | 112 | green); |
philpem@5 | 113 | CImgDisplay disp(img0,"2D Curve Editor",0); |
philpem@5 | 114 | CImgList<float> points, curve; |
philpem@5 | 115 | bool moving = false; |
philpem@5 | 116 | bool help = !file_i; |
philpem@5 | 117 | |
philpem@5 | 118 | if (file_ip) { |
philpem@5 | 119 | std::fprintf(stderr," - Load input control points '%s' : ",cimg::basename(file_ip)); |
philpem@5 | 120 | points = CImg<>(file_ip).transpose().get_split('x'); |
philpem@5 | 121 | std::fprintf(stderr," %u points\n",points.size); |
philpem@5 | 122 | } |
philpem@5 | 123 | |
philpem@5 | 124 | // Enter user loop |
philpem@5 | 125 | //----------------- |
philpem@5 | 126 | while (!disp.is_closed && !disp.is_keyESC && !disp.is_keyQ) { |
philpem@5 | 127 | |
philpem@5 | 128 | // Handle mouse manipulation |
philpem@5 | 129 | //--------------------------- |
philpem@5 | 130 | const float |
philpem@5 | 131 | x = disp.mouse_x*(float)img0.dimx()/disp.dimx(), |
philpem@5 | 132 | y = disp.mouse_y*(float)img0.dimy()/disp.dimy(); |
philpem@5 | 133 | const unsigned int |
philpem@5 | 134 | button = disp.button; |
philpem@5 | 135 | |
philpem@5 | 136 | if (points && button && x>=0 && y>=0) { |
philpem@5 | 137 | |
philpem@5 | 138 | // Find nearest point and nearest segment |
philpem@5 | 139 | float dmin_pt = 1e10f, dmin_seg = dmin_pt; |
philpem@5 | 140 | unsigned int p_pt = 0, p_seg = 0; |
philpem@5 | 141 | cimglist_for(points,p) { |
philpem@5 | 142 | const unsigned int |
philpem@5 | 143 | pnext = closed?(p+1)%points.size:(p+1<points.size?p+1:p); |
philpem@5 | 144 | const float |
philpem@5 | 145 | xp = points(p,0), |
philpem@5 | 146 | yp = points(p,1), |
philpem@5 | 147 | xm = 0.5f*(xp + points(pnext,0)), |
philpem@5 | 148 | ym = 0.5f*(yp + points(pnext,1)); |
philpem@5 | 149 | const float |
philpem@5 | 150 | d_pt = (xp-x)*(xp-x) + (yp-y)*(yp-y), |
philpem@5 | 151 | d_seg = (xm-x)*(xm-x) + (ym-y)*(ym-y); |
philpem@5 | 152 | if (d_pt<dmin_pt) { dmin_pt = d_pt; p_pt = p; } |
philpem@5 | 153 | if (d_seg<dmin_seg) { dmin_seg = d_seg; p_seg = p; } |
philpem@5 | 154 | } |
philpem@5 | 155 | |
philpem@5 | 156 | // Handle button |
philpem@5 | 157 | if (button&1) { |
philpem@5 | 158 | if (dmin_pt<100 || moving) { points(p_pt,0) = x; points(p_pt,1) = y; } |
philpem@5 | 159 | else points.insert(CImg<>::vector(x,y),p_seg+1); |
philpem@5 | 160 | moving = true; |
philpem@5 | 161 | } |
philpem@5 | 162 | if (button&2 && dmin_pt<100) { |
philpem@5 | 163 | if (points.size>3) points.remove(p_pt); |
philpem@5 | 164 | else points.assign(); |
philpem@5 | 165 | disp.button=0; |
philpem@5 | 166 | } |
philpem@5 | 167 | } |
philpem@5 | 168 | if (!button) moving = false; |
philpem@5 | 169 | |
philpem@5 | 170 | if (disp.key) { |
philpem@5 | 171 | switch (disp.key) { |
philpem@5 | 172 | case cimg::keySPACE: interp = !interp; break; |
philpem@5 | 173 | case cimg::keyC: closed = !closed; break; |
philpem@5 | 174 | case cimg::keyT: show_tangents = !show_tangents; break; |
philpem@5 | 175 | case cimg::keyP: show_points = !show_points; break; |
philpem@5 | 176 | case cimg::keyO: show_outline = !show_outline; break; |
philpem@5 | 177 | case cimg::keyN: show_indices = !show_indices; break; |
philpem@5 | 178 | case cimg::keyX: show_coordinates = !show_coordinates; break; |
philpem@5 | 179 | case cimg::keyR: points.assign(); break; |
philpem@5 | 180 | case cimg::keyH: help = !help; break; |
philpem@5 | 181 | case cimg::keyS: { |
philpem@5 | 182 | const char *filename = file_op?file_op:"curve_points.dlm"; |
philpem@5 | 183 | std::fprintf(stderr," - Save control points in '%s'\n",filename); |
philpem@5 | 184 | points.get_append('x').transpose().save(filename); |
philpem@5 | 185 | } break; |
philpem@5 | 186 | } |
philpem@5 | 187 | disp.key = 0; |
philpem@5 | 188 | } |
philpem@5 | 189 | |
philpem@5 | 190 | // Init list of points if empty |
philpem@5 | 191 | //------------------------------ |
philpem@5 | 192 | if (!points) { |
philpem@5 | 193 | const float |
philpem@5 | 194 | x0 = img0.dimx()/4.0f, |
philpem@5 | 195 | y0 = img0.dimy()/4.0f, |
philpem@5 | 196 | x1 = img0.dimx()-x0, |
philpem@5 | 197 | y1 = img0.dimy()-y0; |
philpem@5 | 198 | points.insert(CImg<>::vector(x0,y0)). |
philpem@5 | 199 | insert(CImg<>::vector(x1,y0)). |
philpem@5 | 200 | insert(CImg<>::vector(x1,y1)). |
philpem@5 | 201 | insert(CImg<>::vector(x0,y1)); |
philpem@5 | 202 | } |
philpem@5 | 203 | |
philpem@5 | 204 | // Estimate curve tangents |
philpem@5 | 205 | //------------------------- |
philpem@5 | 206 | CImg<> tangents(points.size,2); |
philpem@5 | 207 | { cimglist_for(points,p) { |
philpem@5 | 208 | const unsigned int |
philpem@5 | 209 | p0 = closed?(p+points.size-1)%points.size:(p?p-1:0), |
philpem@5 | 210 | p1 = closed?(p+1)%points.size:(p+1<points.size?p+1:p); |
philpem@5 | 211 | const float |
philpem@5 | 212 | x = points(p,0), |
philpem@5 | 213 | y = points(p,1), |
philpem@5 | 214 | x0 = points(p0,0), |
philpem@5 | 215 | y0 = points(p0,1), |
philpem@5 | 216 | x1 = points(p1,0), |
philpem@5 | 217 | y1 = points(p1,1), |
philpem@5 | 218 | u0 = x-x0, |
philpem@5 | 219 | v0 = y-y0, |
philpem@5 | 220 | n0 = 1e-8f + (float)std::sqrt(u0*u0+v0*v0), |
philpem@5 | 221 | u1 = x1-x, |
philpem@5 | 222 | v1 = y1-y, |
philpem@5 | 223 | n1 = 1e-8f + (float)std::sqrt(u1*u1+v1*v1), |
philpem@5 | 224 | u = u0/n0 + u1/n1, |
philpem@5 | 225 | v = v0/n0 + v1/n1, |
philpem@5 | 226 | n = 1e-8f + (float)std::sqrt(u*u+v*v), |
philpem@5 | 227 | fact = 0.5f*(n0+n1); |
philpem@5 | 228 | tangents(p,0) = fact*u/n; |
philpem@5 | 229 | tangents(p,1) = fact*v/n; |
philpem@5 | 230 | }} |
philpem@5 | 231 | |
philpem@5 | 232 | // Estimate 3th-order polynomial interpolation |
philpem@5 | 233 | //--------------------------------------------- |
philpem@5 | 234 | curve.assign(); |
philpem@5 | 235 | const unsigned int pmax = points.size-(closed?0:1); |
philpem@5 | 236 | for (unsigned int p0=0; p0<pmax; p0++) { |
philpem@5 | 237 | const unsigned int |
philpem@5 | 238 | p1 = closed?(p0+1)%points.size:(p0+1<points.size?p0+1:p0); |
philpem@5 | 239 | const float |
philpem@5 | 240 | x0 = points(p0,0), |
philpem@5 | 241 | y0 = points(p0,1), |
philpem@5 | 242 | x1 = points(p1,0), |
philpem@5 | 243 | y1 = points(p1,1); |
philpem@5 | 244 | float ax=0, bx=0, cx=0, dx=0, ay=0, by=0, cy=0, dy=0; |
philpem@5 | 245 | if (interp) { |
philpem@5 | 246 | const float |
philpem@5 | 247 | u0 = tangents(p0,0), |
philpem@5 | 248 | v0 = tangents(p0,1), |
philpem@5 | 249 | u1 = tangents(p1,0), |
philpem@5 | 250 | v1 = tangents(p1,1); |
philpem@5 | 251 | ax = 2*(x0-x1)+u0+u1; |
philpem@5 | 252 | bx = 3*(x1-x0)-2*u0-u1; |
philpem@5 | 253 | cx = u0; |
philpem@5 | 254 | dx = x0; |
philpem@5 | 255 | ay = 2*(y0-y1)+v0+v1; |
philpem@5 | 256 | by = 3*(y1-y0)-2*v0-v1; |
philpem@5 | 257 | cy = v0; |
philpem@5 | 258 | dy = y0; |
philpem@5 | 259 | } else { |
philpem@5 | 260 | ax = ay = bx = by = 0; |
philpem@5 | 261 | dx = x0; |
philpem@5 | 262 | dy = y0; |
philpem@5 | 263 | cx = (x1-x0); |
philpem@5 | 264 | cy = (y1-y0); |
philpem@5 | 265 | } |
philpem@5 | 266 | const float tmax = 1+precision; |
philpem@5 | 267 | for (float t=0; t<tmax; t+=precision) { |
philpem@5 | 268 | const float |
philpem@5 | 269 | xt = ax*t*t*t + bx*t*t + cx*t + dx, |
philpem@5 | 270 | yt = ay*t*t*t + by*t*t + cy*t + dy; |
philpem@5 | 271 | curve.insert(CImg<>::vector(xt,yt)); |
philpem@5 | 272 | } |
philpem@5 | 273 | } |
philpem@5 | 274 | |
philpem@5 | 275 | // Draw curve and display image |
philpem@5 | 276 | //------------------------------- |
philpem@5 | 277 | const float |
philpem@5 | 278 | factx = (float)disp.dimx()/img0.dimx(), |
philpem@5 | 279 | facty = (float)disp.dimy()/img0.dimy(); |
philpem@5 | 280 | img = img0.get_resize(disp.dimx(),disp.dimy()); |
philpem@5 | 281 | if (help) img.draw_image(help_img,0.6f); |
philpem@5 | 282 | if (interp && show_outline) { |
philpem@5 | 283 | CImg<> npoints = points.get_append('x'); |
philpem@5 | 284 | npoints.get_shared_line(0)*=factx; |
philpem@5 | 285 | npoints.get_shared_line(1)*=facty; |
philpem@5 | 286 | img.draw_polygon(npoints,red,0.4f); |
philpem@5 | 287 | if (closed) img.draw_polygon(npoints,yellow,0.8f,0x11111111); |
philpem@5 | 288 | else img.draw_line(npoints,yellow,0.8f,0x11111111); |
philpem@5 | 289 | } |
philpem@5 | 290 | CImg<> ncurve = curve.get_append('x'); |
philpem@5 | 291 | ncurve.get_shared_line(0)*=factx; |
philpem@5 | 292 | ncurve.get_shared_line(1)*=facty; |
philpem@5 | 293 | if (closed) img.draw_polygon(ncurve,white,1.0f,~0U); |
philpem@5 | 294 | else img.draw_line(ncurve,white); |
philpem@5 | 295 | |
philpem@5 | 296 | if (show_points) cimglist_for(points,p) { |
philpem@5 | 297 | const float |
philpem@5 | 298 | x = points(p,0)*factx, |
philpem@5 | 299 | y = points(p,1)*facty; |
philpem@5 | 300 | if (show_tangents) { |
philpem@5 | 301 | const float |
philpem@5 | 302 | u = tangents(p,0), |
philpem@5 | 303 | v = tangents(p,1), |
philpem@5 | 304 | n = 1e-8f + (float)std::sqrt(u*u+v*v), |
philpem@5 | 305 | nu = u/n, |
philpem@5 | 306 | nv = v/n; |
philpem@5 | 307 | img.draw_arrow((int)(x-15*nu),(int)(y-15*nv),(int)(x+15*nu),(int)(y+15*nv),green); |
philpem@5 | 308 | } |
philpem@5 | 309 | if (show_indices) img.draw_text((int)x,(int)(y-16),"%d",purple,black,1,6,p); |
philpem@5 | 310 | if (show_coordinates) img.draw_text((int)(x-24),(int)(y+8),"(%d,%d)",yellow,black,0.5f,6,(int)points(p,0),(int)points(p,1)); |
philpem@5 | 311 | img.draw_circle((int)x,(int)y,3,red,0.7f); |
philpem@5 | 312 | } |
philpem@5 | 313 | |
philpem@5 | 314 | img.display(disp); |
philpem@5 | 315 | disp.wait(); |
philpem@5 | 316 | |
philpem@5 | 317 | if (disp.is_resized) disp.resize(false); |
philpem@5 | 318 | } |
philpem@5 | 319 | |
philpem@5 | 320 | // Save output result and exit |
philpem@5 | 321 | //----------------------------- |
philpem@5 | 322 | if (file_op) { |
philpem@5 | 323 | std::fprintf(stderr," - Save control points in '%s'\n",cimg::basename(file_op)); |
philpem@5 | 324 | points.get_append('x').transpose().save(file_op); |
philpem@5 | 325 | } |
philpem@5 | 326 | if (file_oc) { |
philpem@5 | 327 | std::fprintf(stderr," - Save curve points in '%s'\n",cimg::basename(file_oc)); |
philpem@5 | 328 | curve.get_append('x').transpose().save(file_oc); |
philpem@5 | 329 | } |
philpem@5 | 330 | if (file_od) { |
philpem@5 | 331 | std::fprintf(stderr," - Computing distance function, please wait...."); std::fflush(stderr); |
philpem@5 | 332 | CImg<> ncurve = (closed?(+curve).insert(curve[0]):curve).get_append('x'); |
philpem@5 | 333 | const float zero = 0.0f, one = 1.0f; |
philpem@5 | 334 | CImg<> distance = |
philpem@5 | 335 | CImg<>(img0.dimx(),img0.dimy(),1,1,-1.0f).draw_line(ncurve,&zero).draw_fill(0,0,&one). |
philpem@5 | 336 | distance_hamilton(200); |
philpem@5 | 337 | std::fprintf(stderr,"\n - Save distance function in '%s'\n",cimg::basename(file_od)); |
philpem@5 | 338 | distance.save(file_od); |
philpem@5 | 339 | } |
philpem@5 | 340 | |
philpem@5 | 341 | std::fprintf(stderr," - Exit.\n"); |
philpem@5 | 342 | std::exit(0); |
philpem@5 | 343 | return 0; |
philpem@5 | 344 | } |