summaryrefslogtreecommitdiff
path: root/dmagick/Geometry.d
blob: a62c037b6cf5d2df6d33da5e53aa27527f044be0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/**
 * Copyright: Mike Wey 2011
 * License:   zlib (See accompanying LICENSE file)
 * Authors:   Mike Wey
 */

module dmagick.Geometry;

import std.conv;
import std.ascii;
import std.string;

import dmagick.c.geometry;
import dmagick.c.magickString;
import dmagick.c.magickType;

alias ptrdiff_t ssize_t;

/**
 * Geometry provides a convenient means to specify a geometry argument.
 */
struct Geometry
{
	size_t width;    ///
	size_t height;   ///
	ssize_t xOffset; ///
	ssize_t yOffset; ///
	bool percent;    /// The width and/or height are percentages of the original.
	bool minimum;    /// The specified width and/or height is the minimum value.
	bool keepAspect = true;  ///Retain the aspect ratio.
	bool greater;    /// Resize only if the image is greater than the width and/or height.
	bool less;       /// Resize only if the image is smaller than the width and/or height.

	/**
	 * Create a Geometry form a Imagemagick / X11 geometry string.
	 * 
	 * The string constist of a size and a optional offset, the size
	 * can be a width, a height (prefixed with an x), or both
	 * ( $(D widthxheight) ).
	 * 
	 * When a offset is needed ammend the size with an x an y offset
	 * ( signs are required ) like this: $(D {size}+x+Y).
	 * 
	 * The way the size is interpreted can be determined by the
	 * following flags:
	 * 
	 * $(TABLE 
	 *     $(HEADERS Flag,   Explanation)
	 *     $(ROW     $(D %), Normally the attributes are treated as pixels.
	 *                       Use this flag when the width and height
	 *                       attributes represent percentages.)
	 *     $(ROW     $(D !), Use this flag when you want to force the new
	 *                       image to have exactly the size specified by the
	 *                       the width and height attributes.)
	 *     $(ROW     $(D <), Use this flag when you want to change the size
	 *                       of the image only if both its width and height
	 *                       are smaller the values specified by those
	 *                       attributes. The image size is changed
	 *                       proportionally.)
	 *     $(ROW     $(D >), Use this flag when you want to change the size
	 *                       of the image if either its width and height
	 *                       exceed the values specified by those attributes.
	 *                       The image size is changed proportionally.)
	 *     $(ROW     $(D ^), Use ^ to set a minimum image size limit. The
	 *                       geometry $(D 640x480^) means the image width
	 *                       will not be less than 640 and the image height
	 *                       will not be less than 480 pixels after the
	 *                       resize. One of those dimensions will match
	 *                       the requested size. But the image will likely
	 *                       overflow the space requested to preserve its
	 *                       aspect ratio.)
	 * )
	 */
	this(string geometry)
	{
		MagickStatusType flags;

		//If the string starts with a letter assume it's a Page Geometry.
		if ( isAlpha(geometry[0]) )
		{
			char* geo = GetPageGeometry(toStringz(geometry));

			if( geo !is null )
			{
				geometry = to!(string)(geo);
				DestroyString(geo);
			}
		}

		flags = GetGeometry(toStringz(geometry), &xOffset, &yOffset, &width, &height);

		percent    = ( flags & GeometryFlags.PercentValue ) != 0;
		minimum    = ( flags & GeometryFlags.MinimumValue ) != 0;
		keepAspect = ( flags & GeometryFlags.AspectValue  ) == 0;
		greater    = ( flags & GeometryFlags.GreaterValue ) != 0;
		less       = ( flags & GeometryFlags.LessValue    ) != 0;
	}

	unittest
	{
		Geometry geo = Geometry("200x150-50+25!");
		assert( geo.width == 200 && geo.xOffset == -50 );
		assert( geo.keepAspect == false );

		geo = Geometry("A4");
		assert( geo.width == 595 && geo.height == 842);
	}

	/**
	 * Initialize with width heigt and offsets.
	 */
	this(size_t width, size_t height, ssize_t xOffset = 0, ssize_t yOffset = 0)
	{
		this.width   = width;
		this.height  = height;
		this.xOffset = xOffset;
		this.yOffset = yOffset;
	}

	/** */
	package this(RectangleInfo rectangle)
	{
		this.width = rectangle.width;
		this.height = rectangle.height;
		this.xOffset = rectangle.x;
		this.yOffset = rectangle.y;
	}

	/**
	 * Convert Geometry into a Imagemagick geometry string.
	 */
	string toString()
	{
		string geometry;

		if ( width > 0 )
			geometry ~= to!(string)(width);

		if ( height > 0 )
			geometry ~= "x" ~ to!(string)(height);

		geometry ~= format("%s%s%s%s%s",
			percent ? "%" : "",
			minimum ? "^" : "",
			keepAspect ? "" : "!",
			less ? "<" : "",
			greater ? ">" : "");

		if ( xOffset != 0 && yOffset != 0 )
			geometry ~= format("%+s%+s", xOffset, yOffset);

		return geometry;
	}

	unittest
	{
		Geometry geo = Geometry("200x150!-50+25");
		assert( geo.toString == "200x150!-50+25");
	}

	/**
	 * Calculate the absolute width and height based on the flags,
	 * and the profided width and height.
	 */
	Geometry toAbsolute(size_t width, size_t height)
	{
		ssize_t x, y;

		ParseMetaGeometry(toStringz(toString()), &x, &y, &width, &height);

		return Geometry(width, height, x, y);
	}

	unittest
	{
		Geometry percentage = Geometry("50%");
		Geometry absolute = percentage.toAbsolute(100, 100);

		assert(absolute.width  == 50);
		assert(absolute.height == 50);
	}

	/** */
	package RectangleInfo rectangleInfo()
	{
		RectangleInfo info;

		info.width  = width;
		info.height = height;
		info.x = xOffset;
		info.y = yOffset;

		return info;
	}

	/** */
	size_t opCmp(ref const Geometry geometry)
	{
		return width*height - geometry.width*geometry.height;
	}
}