1 /** 2 Image manipulation module. 3 4 Copyright: Copyright Relja Ljubobratovic 2016. 5 6 Authors: Relja Ljubobratovic 7 8 License: $(LINK3 http://www.boost.org/LICENSE_1_0.txt, Boost Software License - Version 1.0). 9 10 $(DL Module contains: 11 $(DD 12 $(LINK2 #resize, resize) 13 $(LINK2 #scale, scale) 14 $(LINK2 #transformAffine,transformAffine) 15 $(LINK2 #transformPerspective,transformPerspective) 16 $(LINK2 #warp,warp) 17 $(LINK2 #remap,remap) 18 ) 19 ) 20 */ 21 module dcv.imgproc.imgmanip; 22 23 import std.exception : enforce; 24 import std.parallelism : TaskPool, taskPool, parallel; 25 import std.range.primitives : ElementType; 26 import std.traits; 27 28 import dcv.core.utils; 29 public import dcv.imgproc.interpolate; 30 31 import mir.ndslice.slice; 32 import mir.ndslice.topology; 33 34 /** 35 Resize array using custom interpolation function. 36 37 Primarilly implemented as image resize. 38 1D, 2D and 3D arrays are supported, where 3D array is 39 treated as channeled image - each channel is interpolated 40 as isolated 2D array (matrix). 41 42 Interpolation function is given as a template parameter. 43 Default interpolation function is linear. Custom interpolation 44 function can be implemented in the 3rd party code, by following 45 interpolation function rules in dcv.imgproc.interpolation. 46 47 Params: 48 slice = Slice to an input array. 49 newsize = tuple that defines new shape. New dimension has to be 50 the same as input slice in the 1D and 2D resize, where in the 51 3D resize newsize has to be 2D. 52 pool = Optional TaskPool instance used to parallelize computation. 53 54 TODO: consider size input as array, and add prealloc 55 */ 56 Slice!(SliceKind.contiguous, packs, V*) resize(alias interp = linear, SliceKind kind, size_t[] packs, V, size_t SN)(Slice!(kind, packs, V*) slice, size_t[SN] newsize, TaskPool pool = taskPool) 57 if (packs.length == 1) 58 //if (isInterpolationFunc!interp) 59 { 60 static if (packs[0] == 1) 61 { 62 static assert(SN == 1, "Invalid new-size setup - dimension does not match with input slice."); 63 return resizeImpl_1!interp(slice, newsize[0], pool); 64 } 65 else static if (packs[0] == 2) 66 { 67 static assert(SN == 2, "Invalid new-size setup - dimension does not match with input slice."); 68 return resizeImpl_2!interp(slice, newsize[0], newsize[1], pool); 69 } 70 else static if (packs[0] == 3) 71 { 72 static assert(SN == 2, "Invalid new-size setup - 3D resize is performed as 2D."); // TODO: find better way to say this... 73 return resizeImpl_3!interp(slice, newsize[0], newsize[1], pool); 74 } 75 else 76 { 77 static assert(0, "Resize is not supported for slice with " ~ N.stringof ~ " dimensions."); 78 } 79 } 80 81 unittest 82 { 83 auto vector = [0.0f, 0.1f, 0.2f].sliced(3); 84 auto matrix = [0.0f, 0.1f, 0.2f, 0.3f].sliced(2, 2); 85 auto image = [0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f].sliced(2, 2, 2); 86 87 auto resv = vector.resize!linear([10]); 88 assert(resv.shape.length == 1); 89 assert(resv.length == 10); 90 91 auto resm = matrix.resize!linear([10, 15]); 92 assert(resm.shape.length == 2); 93 assert(resm.length!0 == 10 && resm.length!1 == 15); 94 95 auto resi = image.resize!linear([20, 14]); 96 assert(resi.shape.length == 3); 97 assert(resi.length!0 == 20 && resi.length!1 == 14 && resi.length!2 == 2); 98 } 99 100 /** 101 Scale array size using custom interpolation function. 102 103 Implemented as convenience function which calls resize 104 using scaled shape of the input slice as: 105 106 $(D_CODE scaled = resize(input, input.shape*scale)) 107 108 */ 109 Slice!(kind, packs, V*) scale(alias interp = linear, V, ScaleValue, SliceKind kind, size_t[] packs, size_t SN)(Slice!(kind, packs, V*) slice, ScaleValue[SN] scale, TaskPool pool = taskPool) 110 if (isFloatingPoint!ScaleValue && isInterpolationFunc!interp) 111 { 112 foreach (v; scale) 113 assert(v > 0., "Invalid scale values (v > 0.0)"); 114 115 static if (packs[0] == 1) 116 { 117 static assert(SN == 1, "Invalid scale setup - dimension does not match with input slice."); 118 size_t newsize = cast(size_t)(slice.length * scale[0]); 119 enforce(newsize > 0, "Scaling value invalid - after scaling array size is zero."); 120 return resizeImpl_1!interp(slice, newsize, pool); 121 } 122 else static if (packs[0] == 2) 123 { 124 static assert(SN == 2, "Invalid scale setup - dimension does not match with input slice."); 125 size_t [2]newsize = [cast(size_t)(slice.length!0 * scale[0]), cast(size_t)(slice.length!1 * scale[1])]; 126 enforce(newsize[0] > 0 && newsize[1] > 0, "Scaling value invalid - after scaling array size is zero."); 127 return resizeImpl_2!interp(slice, newsize[0], newsize[1], pool); 128 } 129 else static if (packs[0] == 3) 130 { 131 static assert(SN == 2, "Invalid scale setup - 3D scale is performed as 2D."); // TODO: find better way to say this... 132 size_t [2]newsize = [cast(size_t)(slice.length!0 * scale[0]), cast(size_t)(slice.length!1 * scale[1])]; 133 enforce(newsize[0] > 0 && newsize[1] > 0, "Scaling value invalid - after scaling array size is zero."); 134 return resizeImpl_3!interp(slice, newsize[0], newsize[1], pool); 135 } 136 else 137 { 138 import std.conv : to; 139 static assert(0, "Resize is not supported for slice with " ~ N.to!string ~ " dimensions."); 140 } 141 } 142 143 unittest 144 { 145 auto vector = [0.0f, 0.1f, 0.2f].sliced(3); 146 auto matrix = [0.0f, 0.1f, 0.2f, 0.3f].sliced(2, 2); 147 auto image = [0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f].sliced(2, 2, 2); 148 149 auto resv = vector.scale!linear([2.0f]); 150 assert(resv.shape.length == 1); 151 assert(resv.length == vector.length * 2); 152 153 auto resm = matrix.scale!linear([3.0f, 4.0f]); 154 assert(resm.shape.length == 2); 155 assert(resm.length!0 == matrix.length!0 * 3 && resm.length!1 == matrix.length!1 * 4); 156 157 auto resi = image.scale!linear([5.0f, 8.0f]); 158 assert(resi.shape.length == 3); 159 assert(resi.length!0 == image.length!0 * 5 && resi.length!1 == image.length!1 * 8 && resi.length!2 == 2); 160 } 161 162 /** 163 Pixel-wise warping of the image. 164 165 Displace each pixel of an image by given [x, y] values. 166 167 Params: 168 interp = (template parameter) Interpolation function, default linear. 169 image = Input image, which is warped. Single and multiple channel images are allowed. 170 map = Displacement map, which holds [x, y] displacement for each pixel of an image. 171 prealloc = Pre-allocated memory where resulting warped image is stored, if defined. 172 Should be of same shape as input image, or an emptySlice, which implies newly allocated image is used. 173 174 Returns: 175 Warped input image by given map. 176 */ 177 pure auto warp(alias interp = linear, ImageTensor, MapTensor)(ImageTensor image, MapTensor map, 178 ImageTensor prealloc = ImageTensor.init) 179 { 180 return pixelWiseDisplacement!(linear, DisplacementType.WARP, ImageTensor, MapTensor) 181 (image, map, prealloc); 182 } 183 184 /** 185 Pixel-wise remapping of the image. 186 187 Move each pixel of an image to a given [x, y] location defined by the map. 188 Function is similar to dcv.imgproc.imgmanip.warp, except displacement of pixels 189 is absolute, rather than relative. 190 191 Params: 192 interp = (template parameter) Interpolation function, default linear. 193 image = Input image, which is remapped. Single and multiple channel images are allowed. 194 map = Target map, which holds [x, y] position for each pixel of an image. 195 prealloc = Pre-allocated memory where resulting remapped image is stored, if defined. 196 Should be of same shape as input image, or an emptySlice, which implies newly allocated image is used. 197 198 Returns: 199 Remapped input image by given map. 200 */ 201 pure auto remap(alias interp = linear, ImageTensor, MapTensor)(ImageTensor image, MapTensor map, 202 ImageTensor prealloc = ImageTensor.init) 203 { 204 return pixelWiseDisplacement!(linear, DisplacementType.REMAP, ImageTensor, MapTensor) 205 (image, map, prealloc); 206 } 207 208 /// Test if warp and remap always returns slice of corresponding format. 209 unittest 210 { 211 import std.random : uniform01; 212 import mir.ndslice.allocation; 213 214 auto image = iota(3, 3).as!float.slice; 215 auto wmap = iota(3, 3, 2).map!(v => cast(float)uniform01).slice; 216 217 auto warped = image.warp(wmap); 218 assert(warped.shape == image.shape); 219 220 auto remapped = image.remap(wmap); 221 assert(remapped.shape == image.shape); 222 } 223 224 unittest 225 { 226 import std.random : uniform01; 227 import mir.ndslice.allocation; 228 229 auto image = iota(3, 3, 3).as!float.slice; 230 auto wmap = iota(3, 3, 2).map!(v => cast(float)uniform01).slice; 231 auto warped = image.warp(wmap); 232 assert(warped.shape == image.shape); 233 234 auto remapped = image.remap(wmap); 235 assert(remapped.shape == image.shape); 236 } 237 238 unittest 239 { 240 import std.random : uniform01; 241 import mir.ndslice.allocation; 242 243 auto image = iota(3, 3).as!float.slice; 244 auto warped = slice!float(3, 3); 245 auto remapped = slice!float(3, 3); 246 auto wmap = iota(3, 3, 2).map!(v => cast(float)uniform01).slice; 247 auto warpedRetVal = image.warp(wmap, warped); 248 assert(warped.shape == image.shape); 249 assert(warpedRetVal.shape == image.shape); 250 assert(&warped[0, 0] == &warpedRetVal[0, 0]); 251 252 auto remappedRetVal = image.remap(wmap, remapped); 253 assert(remapped.shape == image.shape); 254 assert(remappedRetVal.shape == image.shape); 255 assert(&remapped[0, 0] == &remappedRetVal[0, 0]); 256 } 257 258 private enum DisplacementType 259 { 260 WARP, 261 REMAP 262 } 263 264 private pure auto pixelWiseDisplacement(alias interp, DisplacementType disp, ImageTensor, MapTensor) 265 (ImageTensor image, MapTensor map, ImageTensor prealloc) 266 in 267 { 268 assert(!image.empty, "Input image is empty"); 269 assert(map.shape[0 .. 2] == image.shape[0 .. 2], "Invalid map size."); 270 } 271 body 272 { 273 static assert(isSlice!ImageTensor, "Image type has to be of type mir.ndslice.slice.Slice"); 274 static assert(isSlice!MapTensor, "Map type has to be of type mir.ndslice.slice.Slice"); 275 immutable N = isSlice!ImageTensor[0]; 276 static assert(isSlice!MapTensor == [3], 277 "Invalid map tensor dimension - should be matrix of [x, y] displacements (3D)."); 278 279 if (prealloc.shape != image.shape) 280 { 281 import mir.ndslice.allocation; 282 prealloc = uninitializedSlice!(DeepElementType!ImageTensor)(image.shape); 283 } 284 285 static if (N == 2) 286 { 287 displacementImpl!(interp, disp, ImageTensor, MapTensor) 288 (image, map, prealloc); 289 } 290 else static if (N == 3) 291 { 292 foreach (i; 0 .. image.length!2) 293 { 294 auto imagec = image[0 .. $, 0 .. $, i]; 295 auto resultc = prealloc[0 .. $, 0 .. $, i]; 296 displacementImpl!(interp, disp, typeof(imagec), MapTensor) 297 (imagec, map, resultc); 298 } 299 } 300 else 301 static assert(0, "Pixel displacement operations are supported only for 2D and 3D slices."); 302 303 return prealloc; 304 } 305 306 private pure void displacementImpl(alias interp, DisplacementType disp, ImageMatrix, MapTensor) 307 (ImageMatrix image, MapTensor map, ImageMatrix result) 308 { 309 static if (disp == DisplacementType.WARP) 310 float r = 0.0f, c; 311 immutable rf = cast(float)image.length!0; 312 immutable cf = cast(float)image.length!1; 313 for (; !result.empty; result.popFront, map.popFront) 314 { 315 auto rrow = result.front; 316 auto mrow = map.front; 317 static if (disp == DisplacementType.WARP) c = 0.0f; 318 for (; !rrow.empty; rrow.popFront, mrow.popFront) 319 { 320 auto m = mrow.front; 321 static if (disp == DisplacementType.WARP) 322 { 323 float rr = r + m[1]; 324 float cc = c + m[0]; 325 } 326 else 327 { 328 float rr = m[1]; 329 float cc = m[0]; 330 } 331 if (rr >= 0.0f && rr < rf && cc >= 0.0f && cc < cf) 332 { 333 rrow.front = interp(image, rr, cc); 334 } 335 static if (disp == DisplacementType.WARP) ++c; 336 } 337 static if (disp == DisplacementType.WARP) ++r; 338 } 339 } 340 341 private enum TransformType : size_t 342 { 343 AFFINE_TRANSFORM = 0, 344 PERSPECTIVE_TRANSFORM = 1 345 } 346 347 private static bool isTransformMatrix(TransformMatrix)() 348 { 349 // static if its float[][], or its Slice!(SliceKind.contiguous, [2], float*) 350 import std.traits : isScalarType, isPointer, TemplateArgsOf, PointerTarget; 351 352 static if (isArray!TransformMatrix) 353 { 354 static if (isArray!(ElementType!TransformMatrix) 355 && isScalarType!(ElementType!(ElementType!TransformMatrix)) 356 && isFloatingPoint!(ElementType!(ElementType!TransformMatrix))) 357 return true; 358 else 359 return false; 360 } 361 else static if (isSlice!TransformMatrix) 362 { 363 static if (kindOf!TransformMatrix == SliceKind.contiguous && 364 TemplateArgsOf!(TransformMatrix)[1] == [2] && 365 isFloatingPoint!(DeepElementType!TransformMatrix)) 366 return true; 367 else 368 return false; 369 } 370 else 371 { 372 return false; 373 } 374 } 375 376 unittest 377 { 378 static assert(isTransformMatrix!(float[][])); 379 static assert(isTransformMatrix!(double[][])); 380 static assert(isTransformMatrix!(real[][])); 381 static assert(isTransformMatrix!(real[3][3])); 382 static assert(isTransformMatrix!(Slice!(SliceKind.contiguous, [2], float*))); 383 static assert(isTransformMatrix!(Slice!(SliceKind.contiguous, [2], double*))); 384 static assert(isTransformMatrix!(Slice!(SliceKind.contiguous, [2], real*))); 385 386 static assert(!isTransformMatrix!(Slice!(SliceKind.universal, [2], real*))); 387 static assert(!isTransformMatrix!(Slice!(SliceKind.canonical, [2], real*))); 388 389 static assert(!isTransformMatrix!(int[][])); 390 static assert(!isTransformMatrix!(real[])); 391 static assert(!isTransformMatrix!(real[][][])); 392 static assert(!isTransformMatrix!(Slice!(SliceKind.contiguous, [2], int*))); 393 static assert(!isTransformMatrix!(Slice!(SliceKind.contiguous, [1], float*))); 394 } 395 396 /** 397 Transform an image by given affine transformation. 398 399 Params: 400 interp = (template parameter) Interpolation function. Default linear. 401 slice = Slice of an image which is transformed. 402 transform = 2D Transformation matrix (3x3). Its element type must be floating point type, 403 and it can be defined as Slice object, dynamic or static 2D array. 404 outSize = Output image size - if transformation potentially moves parts of image out 405 of input image bounds, output image can be sized differently to maintain information. 406 407 Note: 408 Given transformation is considered to be an affine transformation. If it is not, result is undefined. 409 410 Returns: 411 Transformed image. 412 */ 413 Slice!(kind, packs, V*) transformAffine(alias interp = linear, V, TransformMatrix, SliceKind kind, size_t[] packs)(Slice!(kind, packs, V*) slice, inout TransformMatrix transform, size_t[2] outSize = [0, 0]) 414 { 415 static if (isTransformMatrix!TransformMatrix) 416 { 417 return transformImpl!(TransformType.AFFINE_TRANSFORM, interp)(slice, transform, outSize); 418 } 419 else 420 { 421 static assert(0, "Invalid transform matrix type: " ~ typeof(transform).stringof); 422 } 423 } 424 425 /** 426 Transform an image by given perspective transformation. 427 428 Params: 429 interp = (template parameter) Interpolation function. Default linear. 430 slice = Slice of an image which is transformed. 431 transform = 2D Transformation matrix (3x3). Its element type must be floating point type, 432 and it can be defined as Slice object, dynamic or static 2D array. 433 outSize = Output image size [width, height] - if transformation potentially moves parts of image out 434 of input image bounds, output image can be sized differently to maintain information. 435 436 Note: 437 Given transformation is considered to be an perspective transformation. If it is not, result is undefined. 438 439 Returns: 440 Transformed image. 441 */ 442 Slice!(kind, packs, V*) transformPerspective(alias interp = linear, V, TransformMatrix, SliceKind kind, size_t[] packs)( 443 Slice!(kind, packs, V*) slice, 444 TransformMatrix transform, 445 size_t[2] outSize = [0, 0]) 446 { 447 static if (isTransformMatrix!TransformMatrix) 448 { 449 return transformImpl!(TransformType.PERSPECTIVE_TRANSFORM, linear)(slice, transform, outSize); 450 } 451 else 452 { 453 static assert(0, "Invalid transform matrix type: " ~ typeof(transform).stringof); 454 } 455 } 456 457 version (unittest) 458 { 459 auto transformMatrix = [[1.0f, 0.0f, 5.0f], [0.0f, 1.0f, 5.0f], [0.0f, 0.0f, 1.0f]]; 460 } 461 462 unittest 463 { 464 // test if affine transform without outSize parameter gives out same-shaped result. 465 import mir.ndslice.allocation; 466 auto image = slice!float(3, 3); 467 auto transformed = transformAffine(image, transformMatrix); 468 assert(image.shape == transformed.shape); 469 } 470 471 unittest 472 { 473 // test if affine transform with outSize parameter gives out proper-shaped result. 474 import mir.ndslice.allocation; 475 auto image = slice!float(3, 3); 476 auto transformed = transformAffine(image, transformMatrix, [5, 10]); 477 assert(transformed.length!0 == 10 && transformed.length!1 == 5); 478 } 479 480 unittest 481 { 482 // test if perspective transform without outSize parameter gives out same-shaped result. 483 import mir.ndslice.allocation; 484 auto image = slice!float(3, 3); 485 auto transformed = transformPerspective(image, transformMatrix); 486 assert(image.shape == transformed.shape); 487 } 488 489 unittest 490 { 491 // test if perspective transform with outSize parameter gives out proper-shaped result. 492 import mir.ndslice.allocation; 493 auto image = slice!float(3, 3); 494 auto transformed = transformPerspective(image, transformMatrix, [5, 10]); 495 assert(transformed.length!0 == 10 && transformed.length!1 == 5); 496 } 497 498 private: 499 500 // 1d resize implementation 501 Slice!(SliceKind.contiguous, [1], V*) resizeImpl_1(alias interp, V)(Slice!(SliceKind.contiguous, [1], V*) slice, size_t newsize, TaskPool pool) 502 { 503 504 enforce(!slice.empty && newsize > 0); 505 506 auto retval = new V[newsize]; 507 auto resizeRatio = cast(float)(newsize - 1) / cast(float)(slice.length - 1); 508 509 foreach (i; pool.parallel(newsize.iota)) 510 { 511 retval[i] = interp(slice, cast(float)i / resizeRatio); 512 } 513 514 return retval.sliced(newsize); 515 } 516 517 // 1d resize implementation 518 Slice!(SliceKind.contiguous, [2], V*) resizeImpl_2(alias interp, SliceKind kind, V)(Slice!(kind, [2], V*) slice, size_t height, size_t width, TaskPool pool) 519 { 520 521 enforce(!slice.empty && width > 0 && height > 0); 522 523 auto retval = new V[width * height].sliced(height, width); 524 525 auto rows = slice.length!0; 526 auto cols = slice.length!1; 527 528 auto r_v = cast(float)(height - 1) / cast(float)(rows - 1); // horizontaresize ratio 529 auto r_h = cast(float)(width - 1) / cast(float)(cols - 1); 530 531 foreach (i; pool.parallel(iota(height))) 532 { 533 auto row = retval[i, 0 .. width]; 534 foreach (j; iota(width)) 535 { 536 row[j] = interp(slice, cast(float)i / r_v, cast(float)j / r_h); 537 } 538 } 539 540 return retval; 541 } 542 543 // 1d resize implementation 544 Slice!(SliceKind.contiguous, [3], V*) resizeImpl_3(alias interp, SliceKind kind, V)(Slice!(kind, [3], V*) slice, size_t height, size_t width, TaskPool pool) 545 { 546 547 enforce(!slice.empty && width > 0 && height > 0); 548 549 auto rows = slice.length!0; 550 auto cols = slice.length!1; 551 auto channels = slice.length!2; 552 553 auto retval = new V[width * height * channels].sliced(height, width, channels); 554 555 auto r_v = cast(float)(height - 1) / cast(float)(rows - 1); // horizontaresize ratio 556 auto r_h = cast(float)(width - 1) / cast(float)(cols - 1); 557 558 foreach (c; iota(channels)) 559 { 560 auto sl_ch = slice[0 .. rows, 0 .. cols, c]; 561 auto ret_ch = retval[0 .. height, 0 .. width, c]; 562 foreach (i; pool.parallel(iota(height))) 563 { 564 auto row = ret_ch[i, 0 .. width]; 565 foreach (j; iota(width)) 566 { 567 row[j] = interp(sl_ch, cast(float)i / r_v, cast(float)j / r_h); 568 } 569 } 570 } 571 572 return retval; 573 } 574 575 Slice!(SliceKind.contiguous, [2], float*) invertTransformMatrix(TransformMatrix)(TransformMatrix t) 576 { 577 import mir.ndslice.allocation; 578 auto result = slice!float(3, 3); 579 580 double determinant = +t[0][0] * (t[1][1] * t[2][2] - t[2][1] * t[1][2]) - t[0][1] * ( 581 t[1][0] * t[2][2] - t[1][2] * t[2][0]) + t[0][2] * (t[1][0] * t[2][1] - t[1][1] * t[2][0]); 582 583 enforce(determinant != 0.0f, "Transform matrix determinant is zero."); 584 585 double invdet = 1 / determinant; 586 result[0][0] = (t[1][1] * t[2][2] - t[2][1] * t[1][2]) * invdet; 587 result[0][1] = -(t[0][1] * t[2][2] - t[0][2] * t[2][1]) * invdet; 588 result[0][2] = (t[0][1] * t[1][2] - t[0][2] * t[1][1]) * invdet; 589 result[1][0] = -(t[1][0] * t[2][2] - t[1][2] * t[2][0]) * invdet; 590 result[1][1] = (t[0][0] * t[2][2] - t[0][2] * t[2][0]) * invdet; 591 result[1][2] = -(t[0][0] * t[1][2] - t[1][0] * t[0][2]) * invdet; 592 result[2][0] = (t[1][0] * t[2][1] - t[2][0] * t[1][1]) * invdet; 593 result[2][1] = -(t[0][0] * t[2][1] - t[2][0] * t[0][1]) * invdet; 594 result[2][2] = (t[0][0] * t[1][1] - t[1][0] * t[0][1]) * invdet; 595 596 return result; 597 } 598 599 Slice!(kind, packs, V*) transformImpl(TransformType transformType, alias interp, V, TransformMatrix, SliceKind kind, size_t[] packs) 600 (Slice!(kind, packs, V*) slice, TransformMatrix transform, size_t[2] outSize) 601 in 602 { 603 static assert(packs[0] == 2 || packs[0] == 3, "Unsupported slice dimension (only 2D and 3D supported)"); 604 605 uint rcount = 0; 606 foreach (r; transform) 607 { 608 assert(r.length == 3); 609 rcount++; 610 } 611 assert(rcount == 3); 612 } 613 body 614 { 615 // outsize is [width, height] 616 if (outSize[0] == 0) 617 outSize[0] = slice.length!1; 618 if (outSize[1] == 0) 619 outSize[1] = slice.length!0; 620 621 static if (packs[0] == 2) 622 { 623 auto tSlice = new V[outSize[0] * outSize[1]].sliced(outSize[1], outSize[0]); 624 } 625 else 626 { 627 auto tSlice = new V[outSize[0] * outSize[1] * slice.length!2].sliced(outSize[1], outSize[0], slice.length!2); 628 } 629 630 tSlice[] = cast(V)0; 631 632 auto t = transform.invertTransformMatrix; 633 634 static if (packs[0] == 3) 635 { 636 auto sliceChannels = new typeof(slice[0..$, 0..$, 0])[packs[0]]; 637 foreach (c; iota(slice.length!2)) 638 { 639 sliceChannels[c] = slice[0 .. $, 0 .. $, c]; 640 } 641 } 642 643 double outOffset_x = cast(double)outSize[0] / 2.; 644 double outOffset_y = cast(double)outSize[1] / 2.; 645 double inOffset_x = cast(double)slice.length!1 / 2.; 646 double inOffset_y = cast(double)slice.length!0 / 2.; 647 648 foreach (i; iota(outSize[1])) 649 { // height, rows 650 foreach (j; iota(outSize[0])) 651 { // width, columns 652 double src_x, src_y; 653 double dst_x = cast(double)j - outOffset_x; 654 double dst_y = cast(double)i - outOffset_y; 655 static if (transformType == TransformType.AFFINE_TRANSFORM) 656 { 657 src_x = t[0, 0] * dst_x + t[0, 1] * dst_y + t[0, 2]; 658 src_y = t[1, 0] * dst_x + t[1, 1] * dst_y + t[1, 2]; 659 } 660 else static if (transformType == TransformType.PERSPECTIVE_TRANSFORM) 661 { 662 double d = (t[2, 0] * dst_x + t[2, 1] * dst_y + t[2, 2]); 663 src_x = (t[0, 0] * dst_x + t[0, 1] * dst_y + t[0, 2]) / d; 664 src_y = (t[1, 0] * dst_x + t[1, 1] * dst_y + t[1, 2]) / d; 665 } 666 else 667 { 668 static assert(0, "Invalid transform type"); // should never happen 669 } 670 src_x += inOffset_x; 671 src_y += inOffset_y; 672 if (src_x >= 0 && src_x < slice.length!1 && src_y >= 0 && src_y < slice.length!0) 673 { 674 static if (packs[0] == 2) 675 { 676 tSlice[i, j] = interp(slice, src_y, src_x); 677 } 678 else if (packs[0] == 3) 679 { 680 foreach (c; iota(slice.length!2)) 681 { 682 tSlice[i, j, c] = interp(sliceChannels[c], src_y, src_x); 683 } 684 } 685 } 686 } 687 } 688 689 return tSlice; 690 }