1 /**
2  * Copyright: Mike Wey 2011
3  * License:   zlib (See accompanying LICENSE file)
4  * Authors:   Mike Wey
5  */
6 
7 module dmagick.Geometry;
8 
9 import std.conv;
10 import std.ascii;
11 import std.string;
12 
13 import dmagick.c.geometry;
14 import dmagick.c.magickString;
15 import dmagick.c.magickType;
16 
17 alias ptrdiff_t ssize_t;
18 
19 /**
20  * Geometry provides a convenient means to specify a geometry argument.
21  */
22 struct Geometry
23 {
24 	size_t width;    ///
25 	size_t height;   ///
26 	ssize_t xOffset; ///
27 	ssize_t yOffset; ///
28 	bool percent;    /// The width and/or height are percentages of the original.
29 	bool minimum;    /// The specified width and/or height is the minimum value.
30 	bool keepAspect = true;  ///Retain the aspect ratio.
31 	bool greater;    /// Resize only if the image is greater than the width and/or height.
32 	bool less;       /// Resize only if the image is smaller than the width and/or height.
33 
34 	/**
35 	 * Create a Geometry form a Imagemagick / X11 geometry string.
36 	 * 
37 	 * The string constist of a size and a optional offset, the size
38 	 * can be a width, a height (prefixed with an x), or both
39 	 * ( $(D widthxheight) ).
40 	 * 
41 	 * When a offset is needed ammend the size with an x an y offset
42 	 * ( signs are required ) like this: $(D {size}+x+Y).
43 	 * 
44 	 * The way the size is interpreted can be determined by the
45 	 * following flags:
46 	 * 
47 	 * $(TABLE 
48 	 *     $(HEADERS Flag,   Explanation)
49 	 *     $(ROW     $(D %), Normally the attributes are treated as pixels.
50 	 *                       Use this flag when the width and height
51 	 *                       attributes represent percentages.)
52 	 *     $(ROW     $(D !), Use this flag when you want to force the new
53 	 *                       image to have exactly the size specified by the
54 	 *                       the width and height attributes.)
55 	 *     $(ROW     $(D <), Use this flag when you want to change the size
56 	 *                       of the image only if both its width and height
57 	 *                       are smaller the values specified by those
58 	 *                       attributes. The image size is changed
59 	 *                       proportionally.)
60 	 *     $(ROW     $(D >), Use this flag when you want to change the size
61 	 *                       of the image if either its width and height
62 	 *                       exceed the values specified by those attributes.
63 	 *                       The image size is changed proportionally.)
64 	 *     $(ROW     $(D ^), Use ^ to set a minimum image size limit. The
65 	 *                       geometry $(D 640x480^) means the image width
66 	 *                       will not be less than 640 and the image height
67 	 *                       will not be less than 480 pixels after the
68 	 *                       resize. One of those dimensions will match
69 	 *                       the requested size. But the image will likely
70 	 *                       overflow the space requested to preserve its
71 	 *                       aspect ratio.)
72 	 * )
73 	 */
74 	this(string geometry)
75 	{
76 		MagickStatusType flags;
77 
78 		//If the string starts with a letter assume it's a Page Geometry.
79 		if ( isAlpha(geometry[0]) )
80 		{
81 			char* geo = GetPageGeometry(toStringz(geometry));
82 
83 			if( geo !is null )
84 			{
85 				geometry = to!(string)(geo);
86 				DestroyString(geo);
87 			}
88 		}
89 
90 		flags = GetGeometry(toStringz(geometry), &xOffset, &yOffset, &width, &height);
91 
92 		percent    = ( flags & GeometryFlags.PercentValue ) != 0;
93 		minimum    = ( flags & GeometryFlags.MinimumValue ) != 0;
94 		keepAspect = ( flags & GeometryFlags.AspectValue  ) == 0;
95 		greater    = ( flags & GeometryFlags.GreaterValue ) != 0;
96 		less       = ( flags & GeometryFlags.LessValue    ) != 0;
97 	}
98 
99 	unittest
100 	{
101 		Geometry geo = Geometry("200x150-50+25!");
102 		assert( geo.width == 200 && geo.xOffset == -50 );
103 		assert( geo.keepAspect == false );
104 
105 		geo = Geometry("A4");
106 		assert( geo.width == 595 && geo.height == 842);
107 	}
108 
109 	/**
110 	 * Initialize with width heigt and offsets.
111 	 */
112 	this(size_t width, size_t height, ssize_t xOffset = 0, ssize_t yOffset = 0)
113 	{
114 		this.width   = width;
115 		this.height  = height;
116 		this.xOffset = xOffset;
117 		this.yOffset = yOffset;
118 	}
119 
120 	/** */
121 	package this(RectangleInfo rectangle)
122 	{
123 		this.width = rectangle.width;
124 		this.height = rectangle.height;
125 		this.xOffset = rectangle.x;
126 		this.yOffset = rectangle.y;
127 	}
128 
129 	/**
130 	 * Convert Geometry into a Imagemagick geometry string.
131 	 */
132 	string toString()
133 	{
134 		string geometry;
135 
136 		if ( width > 0 )
137 			geometry ~= to!(string)(width);
138 
139 		if ( height > 0 )
140 			geometry ~= "x" ~ to!(string)(height);
141 
142 		geometry ~= format("%s%s%s%s%s",
143 			percent ? "%" : "",
144 			minimum ? "^" : "",
145 			keepAspect ? "" : "!",
146 			less ? "<" : "",
147 			greater ? ">" : "");
148 
149 		if ( xOffset != 0 && yOffset != 0 )
150 			geometry ~= format("%+s%+s", xOffset, yOffset);
151 
152 		return geometry;
153 	}
154 
155 	unittest
156 	{
157 		Geometry geo = Geometry("200x150!-50+25");
158 		assert( geo.toString == "200x150!-50+25");
159 	}
160 
161 	/**
162 	 * Calculate the absolute width and height based on the flags,
163 	 * and the profided width and height.
164 	 */
165 	Geometry toAbsolute(size_t width, size_t height)
166 	{
167 		ssize_t x, y;
168 
169 		ParseMetaGeometry(toStringz(toString()), &x, &y, &width, &height);
170 
171 		return Geometry(width, height, x, y);
172 	}
173 
174 	unittest
175 	{
176 		Geometry percentage = Geometry("50%");
177 		Geometry absolute = percentage.toAbsolute(100, 100);
178 
179 		assert(absolute.width  == 50);
180 		assert(absolute.height == 50);
181 	}
182 
183 	/** */
184 	package RectangleInfo rectangleInfo()
185 	{
186 		RectangleInfo info;
187 
188 		info.width  = width;
189 		info.height = height;
190 		info.x = xOffset;
191 		info.y = yOffset;
192 
193 		return info;
194 	}
195 
196 	/** */
197 	size_t opCmp(ref const Geometry geometry)
198 	{
199 		return width*height - geometry.width*geometry.height;
200 	}
201 }