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