puzzle.d (9328B)
1 /** 2 * A D-Interface to $(LINK2 https://pureftpd.org/project/libpuzzle, Libpuzzle) 3 * 4 * License: $(LINK2 https://www.gnu.org/licenses/lgpl.html, LGPLV3) 5 * Authors: Dominik Schmidt 6 */ 7 module puzzle; 8 9 version(unittest){ 10 static immutable string testimage="iVBORw0KGgoAAAANSUhEUgAAAE4AAAASAQMAAADPKHrSAAAABlBMVEX///8AAABVwtN+AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wgbCSsvSqRkugAAAGxJREFUCNdjYEAAGyYDMH2AgUGCSQDMdAAyGTCZDQwSbLL9yRL//jkrANVKHDPk6OlxEwBpcxHkkJCAMwUE3H9AmQYGbkATlEDMhAQwE6htxqFDTgIM9s+3tz549+/f5wcg01kQLkJi8jHgAQA3KhrFG5DorwAAAABJRU5ErkJggg=="; 11 ubyte[] testimagedat(){ 12 import std.base64; 13 return cast(ubyte[])Base64.decode(testimage); 14 } 15 } 16 17 extern(C){ 18 struct PuzzleContext { 19 uint puzzle_max_width; 20 uint puzzle_max_height; 21 uint puzzle_lambdas; 22 double puzzle_p_ratio; 23 double puzzle_noise_cutoff; 24 double puzzle_contrast_barrier_for_cropping; 25 double puzzle_max_cropping_ratio; 26 int puzzle_enable_autocrop; 27 ulong magic; 28 }; 29 struct PuzzleDvec { 30 size_t sizeof_vec; 31 size_t sizeof_compressed_vec; 32 double *vec; 33 }; 34 35 struct PuzzleCvec { 36 size_t sizeof_vec; 37 char *vec; 38 }; 39 struct PuzzleCompressedCvec { 40 size_t sizeof_compressed_vec; 41 ubyte *vec; 42 }; 43 44 void 45 puzzle_init_context(PuzzleContext *context); 46 47 void 48 puzzle_free_context(PuzzleContext *context); 49 50 void 51 puzzle_init_cvec(PuzzleContext *context, PuzzleCvec *cvec); 52 53 void 54 puzzle_init_dvec(PuzzleContext *context, PuzzleDvec *dvec); 55 56 int 57 puzzle_fill_dvec_from_file(PuzzleContext *context, PuzzleDvec * dvec, const char *file); 58 59 int 60 puzzle_fill_cvec_from_file(PuzzleContext *context, PuzzleCvec * cvec, const char *file); 61 62 int 63 puzzle_fill_dvec_from_mem(PuzzleContext *context, PuzzleDvec * dvec, const void *mem, 64 size_t size); 65 66 int 67 puzzle_fill_cvec_from_mem(PuzzleContext *context, PuzzleCvec * cvec, const void *mem, 68 size_t size); 69 70 int 71 puzzle_fill_cvec_from_dvec(PuzzleContext *context, PuzzleCvec * cvec, 72 const PuzzleDvec *dvec); 73 74 void 75 puzzle_free_cvec(PuzzleContext *context, PuzzleCvec *cvec); 76 77 void 78 puzzle_free_dvec(PuzzleContext *context, PuzzleDvec *dvec); 79 80 void 81 puzzle_init_compressed_cvec(PuzzleContext *context, 82 PuzzleCompressedCvec * compressed_cvec); 83 84 void 85 puzzle_free_compressed_cvec(PuzzleContext *context, 86 PuzzleCompressedCvec * compressed_cvec); 87 88 int 89 puzzle_compress_cvec(PuzzleContext *context, PuzzleCompressedCvec * compressed_cvec, 90 const PuzzleCvec * cvec); 91 92 int 93 puzzle_uncompress_cvec(PuzzleContext *context, PuzzleCompressedCvec * compressed_cvec, 94 const(PuzzleCvec *) cvec); 95 96 double 97 puzzle_vector_normalized_distance(PuzzleContext *context, const PuzzleCvec * cvec1, 98 const PuzzleCvec * cvec2, int fix_for_texts); 99 100 } 101 import std.string; 102 103 static bool valid(in PuzzleContext ctx){ 104 return (ctx.magic==0xdeadbeef); 105 } 106 static bool valid(in PuzzleContext *ctx){ 107 return (ctx!=null && valid(*ctx)); 108 } 109 110 class PuzzleException : Exception{ 111 pure nothrow @safe this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null){ 112 super(msg,file,line,next); 113 } 114 } 115 116 /** 117 * PuzzleContext wrapper 118 * 119 * Has to be inititialized with `Puzzle.initialize();` 120 * and frees the context on destruction. 121 * 122 * Be careful, when you stack-allocate it, the object gets 123 * rendered unusable once the scope has been left. 124 * 125 */ 126 struct Puzzle{ 127 PuzzleContext ctx; 128 /** 129 * Initializes the Puzzle-context. 130 * 131 */ 132 void initialize() 133 out{ 134 assert(ctx.valid()); 135 } 136 body{ 137 puzzle_init_context(&ctx); 138 } 139 140 ///Frees the PuzzleContext (If there is one) 141 ~this(){ 142 if(ctx.valid){ 143 puzzle_free_context(&ctx); 144 } 145 } 146 /** 147 * Returns a newly allocated DVec-Structure 148 * 149 * $(DDOC_SEE_ALSO struct DVec) 150 * 151 */ 152 DVec dvec(){ 153 return DVec(&ctx); 154 } 155 156 /** 157 * Returns a newly allocated CVec-Structure 158 * 159 * $(DDOC_SEE_ALSO struct CVec) 160 * 161 */ 162 CVec cvec(){ 163 return CVec(&ctx); 164 } 165 } 166 /// 167 unittest{ 168 Puzzle p; 169 assert(!p.ctx.valid); 170 p.initialize(); 171 assert(p.ctx.valid); 172 173 DVec d=p.dvec(); 174 assert(d.ctx.valid); 175 176 DVec c=p.dvec(); 177 assert(c.ctx.valid); 178 } 179 180 /** 181 * Wraps the PuzzleDVec-structure 182 * 183 */ 184 struct DVec{ 185 PuzzleDvec vec; 186 PuzzleContext *ctx; 187 @disable this(); 188 189 ///Initializes the DVec 190 this(PuzzleContext *ctx) 191 in{ 192 assert(ctx.valid(), "Context invalid"); 193 } 194 body{ 195 this.ctx=ctx; 196 puzzle_init_dvec(ctx,&vec); 197 } 198 199 ///Frees the DVec (If it has been allocated by the library); 200 ~this(){ 201 if(ctx!=null){ 202 puzzle_free_dvec(ctx,&vec); 203 } 204 } 205 206 /** 207 * Generates the vector from a file. 208 * Params: 209 * f = File to read the data from. 210 * Throws: 211 * PuzzleException if the puzzle-function fails to load the image 212 */ 213 void load(string f){ 214 if(puzzle_fill_dvec_from_file(ctx, &vec, toStringz(f))!=0){ 215 throw new PuzzleException("Couldn't read from file"); 216 } 217 } 218 219 /** 220 * Generates the vector from memory 221 * Params: 222 * mem = The memory of an Image 223 * Throws: 224 * PuzzleException if the memory couldn't be read. 225 */ 226 void load(void[] mem){ 227 if(puzzle_fill_dvec_from_mem(ctx, &vec, cast(void*)mem, mem.length)!=0){ 228 throw new PuzzleException("Couldn't read from memory"); 229 } 230 } 231 /** 232 * Get the CVec derived from this DVector. 233 * Returns: 234 * The corresponding(and comparable) CVector to this DVector 235 */ 236 CVec cvec(){ 237 CVec cvec=CVec(ctx); 238 puzzle_fill_cvec_from_dvec(ctx,&cvec.vec,&vec); 239 return cvec; 240 } 241 242 /** 243 * Get the length of the vector 244 * Returns: 245 * The length of the vector 246 */ 247 @safe @nogc @property size_t length(){ 248 return vec.sizeof_vec; 249 } 250 } 251 252 /// 253 struct CVec{ 254 PuzzleCvec vec; 255 PuzzleContext *ctx; 256 @disable this(); 257 this(ref CVec_Compressed c){ 258 this(c.ctx); 259 puzzle_uncompress_cvec(ctx, &c.vec, &vec); 260 } 261 /** 262 * Generates a Vector by hand. 263 * 264 * This can be used to save a Vector and reload it again. 265 * 266 * Params: 267 * a = The array of data to fill the vector with. 268 */ 269 @nogc pure nothrow this(byte[] a){ 270 this(cast(char*)a.ptr, a.length); 271 } 272 ///ditto 273 @nogc pure nothrow this(char *a, size_t length){ 274 vec.vec=a; 275 vec.sizeof_vec=length; 276 } 277 278 /// 279 unittest{ 280 Puzzle p; 281 p.initialize(); 282 CVec c=p.cvec(); 283 284 c.load(testimagedat()); 285 assert(c.length>0); 286 287 CVec c2=CVec(c.vec.vec,c.length); 288 assert(std.math.approxEqual(c.compare(c2),0)); 289 } 290 291 ///Initializes a Vector 292 this(PuzzleContext *ctx) 293 in{ 294 assert(ctx.valid(), "Context invalid"); 295 } 296 body{ 297 this.ctx=ctx; 298 puzzle_init_cvec(ctx,&vec); 299 } 300 ///Frees the vector(If it was allocated by the library) 301 ~this(){ 302 if(ctx!=null){ 303 puzzle_free_cvec(ctx,&vec); 304 } 305 } 306 307 /** 308 * Generates the vector from a file. 309 * Params: 310 * f = File to read the data from. 311 * Throws: 312 * PuzzleException if the puzzle-function fails to load the image 313 */ 314 void load(string f){ 315 if(puzzle_fill_cvec_from_file(ctx, &vec, toStringz(f))!=0){ 316 throw new PuzzleException("Couldn't read from file"); 317 } 318 } 319 320 /** 321 * Generates the vector from memory 322 * Params: 323 * mem = The memory of an Image 324 * Throws: 325 * PuzzleException if the memory couldn't be read. 326 */ 327 void load(void[] mem){ 328 if(puzzle_fill_cvec_from_mem(ctx, &vec, cast(void*)mem, mem.length)!=0){ 329 throw new PuzzleException("Couldn't read from memory"); 330 } 331 } 332 333 /** 334 * Compare two CVectors. 335 * Params: 336 * b = The second vector to compare to 337 * hasText = Wether one of the images had any text in it. 338 * Returns: 339 * The distance of both vectors. 340 */ 341 double compare(in ref CVec b, bool hasText=false){ 342 return puzzle_vector_normalized_distance(ctx,&vec,&b.vec,(hasText) ? 1 : 0); 343 } 344 345 346 /** 347 * Compresses a CVector 348 * Returns: 349 * A compressed CVector 350 */ 351 CVec_Compressed compress(){ 352 return CVec_Compressed(this); 353 } 354 355 /** 356 * Get the length of the vector 357 * Returns: 358 * The length of the vector 359 */ 360 @nogc @safe @property size_t length(){ 361 return vec.sizeof_vec; 362 } 363 } 364 365 /// 366 unittest{ 367 Puzzle p; 368 p.initialize(); 369 CVec c=p.cvec(); 370 c.load(testimagedat()); 371 372 assert(std.math.approxEqual(c.compare(c),0)); 373 } 374 375 ///A compressed CVector 376 struct CVec_Compressed{ 377 PuzzleCompressedCvec vec; 378 PuzzleContext *ctx; 379 @disable this(); 380 381 ///Generates a compressed CVector corresponding to c 382 this(ref CVec c){ 383 this(c.ctx); 384 puzzle_compress_cvec(ctx, &vec, &c.vec); 385 } 386 387 /** 388 * Generates a Vector by hand. 389 * 390 * Params: 391 * a = The array of data to fill the vector with. 392 */ 393 this(ubyte[] a){ 394 this(a.ptr,a.length); 395 } 396 397 ///ditto 398 this(ubyte* a, size_t length){ 399 vec.vec=a; 400 vec.sizeof_compressed_vec=length; 401 } 402 403 ///Allocates a new Compressed CVector in the library 404 this(PuzzleContext *ctx) 405 in{ 406 assert(ctx.valid(), "Context invalid"); 407 } 408 body{ 409 this.ctx=ctx; 410 puzzle_init_compressed_cvec(ctx,&vec); 411 } 412 413 ///Frees the Vector, if it was allocated by the library 414 ~this(){ 415 if(ctx!=null){ 416 puzzle_free_compressed_cvec(ctx,&vec); 417 } 418 } 419 420 ///Returns the corresponding uncompressed CVector 421 CVec uncompress(){ 422 return CVec(this); 423 } 424 425 /// 426 @safe @nogc @property size_t length(){ 427 return vec.sizeof_compressed_vec; 428 } 429 } 430 431 /// 432 unittest{ 433 Puzzle p; 434 p.initialize(); 435 CVec c=p.cvec(); 436 c.load(testimagedat()); 437 CVec_Compressed cc=c.compress(); 438 CVec c2=cc.uncompress(); 439 440 assert(std.math.approxEqual(c.compare(c2),0)); 441 }