1 /** 2 * Copyright: Mike Wey 2011 3 * License: zlib (See accompanying LICENSE file) 4 * Authors: Mike Wey 5 */ 6 7 module dmagick.ImageView; 8 9 import std.array; 10 import std.parallelism; 11 import std.range; 12 import std..string; 13 import core.atomic; 14 15 import dmagick.Color; 16 import dmagick.Exception; 17 import dmagick.Geometry; 18 import dmagick.Image; 19 20 import dmagick.c.cache; 21 import dmagick.c.exception; 22 import dmagick.c.geometry; 23 import dmagick.c.image : MagickCoreImage = Image; 24 import dmagick.c.magickType; 25 import dmagick.c.magickVersion; 26 import dmagick.c.memory; 27 import dmagick.c.pixel; 28 29 //These symbols are publicly imported by dmagick.Image. 30 private alias dmagick.c.magickType.Quantum Quantum; 31 32 alias ptrdiff_t ssize_t; 33 34 /** 35 * The ImageView allows changing induvidual pixels with the slicing and 36 * indexing operators. 37 * 38 * -------------------- 39 * ImageView view = image.view(); 40 * 41 * //Assign a square. 42 * view[4..40][5..50] = new Color("red"); 43 * 44 * //Reduce a view. 45 * view = view[10..view.extend.height-10][20..view.extend.width-20]; 46 * 47 * //Assign a single row. 48 * view[30] = new Color("blue"); 49 * //Or a column. 50 * view[][30] = new Color("blue"); 51 * //And induvidual pixels. 52 * view[3][5] = new Color("green"); 53 * 54 * //We can also use foreach. 55 * foreach ( row; view ) 56 * { 57 * //This is executed in parallel. 58 * foreach ( ref pixel; row ) 59 * pixel = new Color("black"); 60 * } 61 * -------------------- 62 */ 63 class ImageView 64 { 65 Image image; 66 RectangleInfo extent; 67 68 /** 69 * Create a new view for image. 70 */ 71 this(Image image, Geometry area) 72 { 73 if ( area.width + area.xOffset > image.columns || 74 area.height + area.yOffset > image.rows ) 75 { 76 throw new OptionException("Specified area is larger than the image"); 77 } 78 79 this.image = image; 80 this.extent = area.rectangleInfo; 81 } 82 83 /** 84 * The width of the view. 85 */ 86 @property size_t width() const 87 { 88 return extent.width; 89 } 90 91 /** 92 * The height of the view. 93 */ 94 @property size_t height() const 95 { 96 return extent.height; 97 } 98 99 /** 100 * The height or the width of the view, depending on in which slice 101 * it's used. 102 * 103 * Bugs: dmd bug 7097: opDollar doesn't work with slicing. 104 */ 105 size_t opDollar() const 106 { 107 return extent.height; 108 } 109 110 /** 111 * Indexing operators yield or modify the value at a specified index. 112 */ 113 Pixels opIndex(size_t row) 114 { 115 return Pixels(image, extent.x, extent.y + row, extent.width, 1); 116 } 117 118 ///ditto 119 void opIndexAssign(Color color, size_t index) 120 { 121 this[index][] = color; 122 } 123 124 /** 125 * Sliceing operators yield or modify the value in the specified slice. 126 */ 127 ImageView opSlice() 128 { 129 return opSlice(0, extent.height); 130 } 131 132 ///ditto 133 ImageView opSlice(size_t upper, size_t lower) 134 { 135 RectangleInfo newExtent = extent; 136 137 newExtent.y += upper; 138 newExtent.height = lower - upper; 139 140 return new Rows(image, Geometry(newExtent)); 141 } 142 143 ///ditto 144 void opSliceAssign(Color color) 145 { 146 foreach ( row; this ) 147 row[] = color; 148 } 149 150 ///ditto 151 void opSliceAssign(Color color, size_t upper, size_t lower) 152 { 153 foreach ( row; this[upper .. lower] ) 154 row[] = color; 155 } 156 157 /** 158 * Support the usage of foreach to loop over the rows in the view. 159 * The foreach is executed in parallel. 160 */ 161 int opApply(int delegate(Pixels) dg) 162 { 163 shared(int) progress; 164 165 foreach ( row; taskPool.parallel(iota(extent.y, extent.y + extent.height)) ) 166 { 167 int result = dg(Pixels(image, extent.x, row, extent.width, 1)); 168 169 if ( result ) 170 return result; 171 172 if ( image.monitor !is null ) 173 { 174 atomicOp!"+="(progress, 1); 175 image.monitor()("ImageView/" ~ image.filename, progress, extent.height); 176 } 177 } 178 179 return 0; 180 } 181 } 182 183 184 /* 185 * A Rows object is returned when a ImageView is sliced, this is to support 186 * sliceing the columns with a sceond slice. 187 */ 188 class Rows : ImageView 189 { 190 this(Image image, Geometry area) 191 { 192 super(image, area); 193 } 194 195 /* 196 * The height or the width of the view, depending on in which slice 197 * it's used. 198 * 199 * Bugs: dmd bug 7097: opDollar doesn't work with slicing. 200 */ 201 override size_t opDollar() const 202 { 203 return extent.width; 204 } 205 206 /* 207 * Indexing operators yield or modify the value at a specified index. 208 */ 209 override Pixels opIndex(size_t column) 210 { 211 return Pixels(image, extent.x + column, extent.y, 1, extent.height); 212 } 213 214 /* 215 * Sliceing operators yield or modify the value in the specified slice. 216 */ 217 override ImageView opSlice() 218 { 219 return opSlice(0, extent.width); 220 } 221 222 ///ditto 223 override ImageView opSlice(size_t left, size_t right) 224 { 225 RectangleInfo newExtent = extent; 226 227 newExtent.x += left; 228 newExtent.width = right - left; 229 230 return new ImageView(image, Geometry(newExtent)); 231 } 232 } 233 234 /** 235 * Row reprecents a singe row of pixels in an ImageView. 236 */ 237 struct Pixels 238 { 239 Image image; 240 PixelPacket[] pixels; 241 242 private size_t* refcount; 243 private NexusInfo nexus; 244 245 /** 246 * Get the pixels of the specifies area in the image. 247 */ 248 this(Image image, ssize_t x, ssize_t y, size_t columns, size_t rows) 249 { 250 this.image = image; 251 252 Quantum* data = 253 GetAuthenticPixelCacheNexus(image.imageRef, x, y, columns, rows, &nexus, DMagickExceptionInfo()); 254 this.pixels = (cast(PixelPacket*)data)[0..columns*rows]; 255 256 refcount = new size_t; 257 *refcount = 1; 258 } 259 260 /* 261 * Copy constructor. 262 */ 263 private this(Image image, PixelPacket[] pixels, size_t* refCount, NexusInfo nexus) 264 { 265 this.image = image; 266 this.pixels = pixels; 267 this.refcount = refcount; 268 this.nexus = nexus; 269 270 (*refcount)++; 271 } 272 273 this(this) 274 { 275 if ( !pixels.empty ) 276 (*refcount)++; 277 } 278 279 ~this() 280 { 281 if ( pixels.empty ) 282 return; 283 284 (*refcount)--; 285 286 if ( *refcount == 0 ) 287 { 288 sync(); 289 290 if ( !nexus.mapped ) 291 RelinquishMagickMemory(nexus.cache); 292 else 293 UnmapBlob(nexus.cache, cast(size_t)nexus.length); 294 295 nexus.cache = null; 296 } 297 } 298 299 /** 300 * The number of pixels in this row / column. 301 * 302 * Bugs: dmd bug 7097: opDollar doesn't work with slicing. 303 */ 304 @property size_t length() const 305 { 306 return pixels.length; 307 } 308 309 ///ditto 310 size_t opDollar() const 311 { 312 return pixels.length; 313 } 314 315 /** 316 * Indexing operators yield or modify the value at a specified index. 317 */ 318 Color opIndex(size_t pixel) 319 { 320 return new Color(pixels.ptr + pixel); 321 } 322 323 ///ditto 324 void opIndexAssign(Color color, size_t index) 325 { 326 pixels[index] = color.pixelPacket; 327 } 328 329 /** 330 * Sliceing operators yield or modify the value in the specified slice. 331 */ 332 Pixels opSlice() 333 { 334 return this; 335 } 336 337 ///ditto 338 Pixels opSilce(size_t left, size_t right) 339 { 340 return Pixels(image, pixels[left .. right], refcount, nexus); 341 } 342 343 ///ditto 344 void opSliceAssign(Color color) 345 { 346 opSliceAssign(color, 0, pixels.length); 347 } 348 349 ///ditto 350 void opSliceAssign(Color color, size_t left, size_t right) 351 { 352 foreach( i; left .. right ) 353 this[i] = color; 354 } 355 356 /** 357 * Sync the pixels back to the image. The destructor does this for you. 358 */ 359 void sync() 360 { 361 SyncAuthenticPixelCacheNexus(image.imageRef, &nexus, DMagickExceptionInfo()); 362 } 363 364 /** 365 * Support using foreach on a row. 366 */ 367 int opApply(T : Color)(int delegate(ref T) dg) 368 { 369 T color = new T(); 370 371 foreach ( ref PixelPacket pixel; pixels ) 372 { 373 color.pixelPacket = pixel; 374 375 int result = dg(color); 376 377 pixel = color.pixelPacket; 378 379 if ( result ) 380 return result; 381 } 382 383 return 0; 384 } 385 386 unittest 387 { 388 Image image = new Image(Geometry(100, 100), new Color("Blue")); 389 { 390 Pixels row = Pixels(image, 25, 50, 50, 1); 391 row[] = new Color("red"); 392 } 393 394 assert(image.view[50][50] == new Color("red")); 395 } 396 } 397 398 /* 399 * Note: these defenitions aren't public. 400 */ 401 private extern(C) 402 { 403 struct NexusInfo 404 { 405 MagickBooleanType 406 mapped; 407 408 RectangleInfo 409 region; 410 411 MagickSizeType 412 length; 413 414 Quantum* 415 cache, 416 pixels; 417 418 static if ( MagickLibVersion >= 0x686 ) 419 { 420 MagickBooleanType 421 authentic_pixel_cache; 422 } 423 424 void* 425 metacontent; 426 427 size_t 428 signature; 429 } 430 431 Quantum* GetAuthenticPixelCacheNexus(MagickCoreImage* image, const ssize_t x, const ssize_t y, const size_t columns, const size_t rows, NexusInfo* nexus_info, ExceptionInfo* exception); 432 MagickBooleanType SyncAuthenticPixelCacheNexus(MagickCoreImage* image, NexusInfo* nexus_info, ExceptionInfo* exception); 433 MagickBooleanType UnmapBlob(void* map, const size_t length); 434 } 435