Cyotek Development Bloghttps://devblog.cyotek.com/tag/rectangle/atom.xml2013-02-10T08:37:04ZDividing up a rectangle based on pairs of points using C#urn:uuid:ff67a638-dab6-4764-9165-0c2399d4765d2013-02-10T08:37:04Z2013-02-10T08:37:04Z<p>Recently we released the first alpha of our latest product,
<a href="https://cyotek.com/cyotek-slicr">Cyotek Slicr</a>, a tool for slicing up an image. At the heart
of this tool is a series of routines that take a given image and
pairs of input points, from which the image is chopped up
accordingly. This article describes how to break up a rectangle
into smaller parts based on user defined co-ordinates.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/rectangle1.png" class="gallery" title="An example of the programs output" ><img src="https://images.cyotek.com/image/thumbnail/devblog/rectangle1.png" alt="An example of the programs output" decoding="async" loading="lazy" /></a><figcaption>An example of the programs output</figcaption></figure><h2 id="caveat-emptor">Caveat Emptor</h2>
<blockquote>
<p>Before I get started, I just want to point out that the code
I'm about to show you is proof of concept code. It doesn't use
algorithms such as <a href="http://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm" rel="external nofollow noopener">Bentley–Ottmann</a>, it's not very
efficient, and there's probably a hundred ways of doing it
better. However, it works, which is pretty much all I care
about at this moment!</p>
</blockquote>
<h2 id="getting-started">Getting Started</h2>
<p>These are the rules for splitting up a rectangle into component
parts:</p>
<ol>
<li>Lines must be straight, so each pair of co-ordinates will
align on one axis</li>
<li>Co-ordinates must start from either an edge, or the
intersection of another pair. The second co-ordinate must end
in a similar fashion. Any &quot;orphan&quot; co-ordinates which don't
start/end on another edge/join are illegal and should be
ignored</li>
<li>Co-ordinates can start and end at any point in the bounds of
the canvas, as long as they follow the previous rules.</li>
</ol>
<p>In order to achieve this, I ended up with creating a number of
support classes</p>
<ul>
<li><code>Segment</code> - this class starts the starting point of a line,
it's length, and it's orientation. Originally I just started
by storing the two pairs of co-ordinates, but in the end it
was easier with the length and orientation.</li>
<li><code>SegmentPoint</code> - this class stores the details of a single
point. An instance of this class is created for each unique
point created by the start and end of a segment, and any
intersections. It also stores the directions of neighbouring
points.</li>
</ul>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">class</span> Segment
<span class="symbol">{</span>
 <span class="keyword">public</span> Point EndLocation
 <span class="symbol">{</span>
 <span class="keyword">get</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> x<span class="symbol">;</span>
 <span class="keyword">int</span> y<span class="symbol">;</span>

 <span class="keyword">switch</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Orientation<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> SegmentOrientation<span class="symbol">.</span>Vertical<span class="symbol">:</span>
 x <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Location<span class="symbol">.</span>X<span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Location<span class="symbol">.</span>Y <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Size<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">default</span><span class="symbol">:</span>
 x <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Location<span class="symbol">.</span>X <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Size<span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Location<span class="symbol">.</span>Y<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> <span class="keyword">new</span> Point<span class="symbol">(</span>x<span class="symbol">,</span> y<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> Point Location <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> SegmentOrientation Orientation <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">int</span> Size <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">internal</span> <span class="keyword">class</span> SegmentPoint
<span class="symbol">{</span>
 <span class="keyword">public</span> SegmentPointConnections Connections <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> Point Location <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">int</span> X <span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>Location<span class="symbol">.</span>X<span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">int</span> Y <span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>Location<span class="symbol">.</span>Y<span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>With these helper classes, I can now construct the code to
process them and extract the rectangles.</p>
<h2 id="calculating-points">Calculating Points</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/rectangle2.png" class="gallery" title="In this example, a rectangle has been created by segments crossing each other. We need to detect the intersections to find these types of rectangles" ><img src="https://images.cyotek.com/image/thumbnail/devblog/rectangle2.png" alt="In this example, a rectangle has been created by segments crossing each other. We need to detect the intersections to find these types of rectangles" decoding="async" loading="lazy" /></a><figcaption>In this example, a rectangle has been created by segments crossing each other. We need to detect the intersections to find these types of rectangles</figcaption></figure>
<p>The image above demonstrates the first problem. The four
segments intersect with each other, causing a rectangle to be
generated untouched by any user defined point. However, if we
are going to find that rectangle, we need to find any new point
generated by multiple segments intersecting.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> CalculatePoints<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 List<span class="symbol">&lt;</span>Segment<span class="symbol">&gt;</span> segments<span class="symbol">;</span>

 segments <span class="symbol">=</span> <span class="keyword">new</span> List<span class="symbol">&lt;</span>Segment<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Points <span class="symbol">=</span> <span class="keyword">new</span> Dictionary<span class="symbol">&lt;</span>Point<span class="symbol">,</span> SegmentPoint<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// add segments representing the edges</span>
 segments<span class="symbol">.</span>Add<span class="symbol">(</span><span class="keyword">new</span> Segment <span class="symbol">{</span> Location <span class="symbol">=</span> Point<span class="symbol">.</span>Empty<span class="symbol">,</span> Size <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Size<span class="symbol">.</span>Width<span class="symbol">,</span> Orientation <span class="symbol">=</span> SegmentOrientation<span class="symbol">.</span>Horizontal <span class="symbol">}</span><span class="symbol">)</span><span class="symbol">;</span>
 segments<span class="symbol">.</span>Add<span class="symbol">(</span><span class="keyword">new</span> Segment <span class="symbol">{</span> Location <span class="symbol">=</span> <span class="keyword">new</span> Point<span class="symbol">(</span><span class="number">0</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Size<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">,</span> Size <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Size<span class="symbol">.</span>Width<span class="symbol">,</span> Orientation <span class="symbol">=</span> SegmentOrientation<span class="symbol">.</span>Horizontal <span class="symbol">}</span><span class="symbol">)</span><span class="symbol">;</span>
 segments<span class="symbol">.</span>Add<span class="symbol">(</span><span class="keyword">new</span> Segment <span class="symbol">{</span> Location <span class="symbol">=</span> Point<span class="symbol">.</span>Empty<span class="symbol">,</span> Size <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Size<span class="symbol">.</span>Height<span class="symbol">,</span> Orientation <span class="symbol">=</span> SegmentOrientation<span class="symbol">.</span>Vertical <span class="symbol">}</span><span class="symbol">)</span><span class="symbol">;</span>
 segments<span class="symbol">.</span>Add<span class="symbol">(</span><span class="keyword">new</span> Segment <span class="symbol">{</span> Location <span class="symbol">=</span> <span class="keyword">new</span> Point<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Size<span class="symbol">.</span>Width<span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">,</span> Size <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Size<span class="symbol">.</span>Height<span class="symbol">,</span> Orientation <span class="symbol">=</span> SegmentOrientation<span class="symbol">.</span>Vertical <span class="symbol">}</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// add the rest of the segments</span>
 segments<span class="symbol">.</span>AddRange<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Segments<span class="symbol">)</span><span class="symbol">;</span>

 segments<span class="symbol">.</span>Sort<span class="symbol">(</span><span class="symbol">(</span>a<span class="symbol">,</span> b<span class="symbol">)</span> <span class="symbol">=&gt;</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> result <span class="symbol">=</span> a<span class="symbol">.</span>Location<span class="symbol">.</span>X<span class="symbol">.</span>CompareTo<span class="symbol">(</span>b<span class="symbol">.</span>Location<span class="symbol">.</span>X<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>result <span class="symbol">==</span> <span class="number">0</span><span class="symbol">)</span>
 result <span class="symbol">=</span> a<span class="symbol">.</span>Location<span class="symbol">.</span>Y<span class="symbol">.</span>CompareTo<span class="symbol">(</span>b<span class="symbol">.</span>Location<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">return</span> result<span class="symbol">;</span>
 <span class="symbol">}</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">foreach</span> <span class="symbol">(</span>Segment segment <span class="keyword">in</span> segments<span class="symbol">)</span>
 <span class="symbol">{</span>
 Segment currentSegment<span class="symbol">;</span>

 <span class="comment">// add the segment points</span>
 <span class="keyword">this</span><span class="symbol">.</span>UpdatePoint<span class="symbol">(</span>segment<span class="symbol">.</span>Location<span class="symbol">,</span> segment<span class="symbol">.</span>Orientation <span class="symbol">==</span> SegmentOrientation<span class="symbol">.</span>Horizontal <span class="symbol">?</span> SegmentPointConnections<span class="symbol">.</span>Left <span class="symbol">:</span> SegmentPointConnections<span class="symbol">.</span>Top<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>UpdatePoint<span class="symbol">(</span>segment<span class="symbol">.</span>EndLocation<span class="symbol">,</span> segment<span class="symbol">.</span>Orientation <span class="symbol">==</span> SegmentOrientation<span class="symbol">.</span>Horizontal <span class="symbol">?</span> SegmentPointConnections<span class="symbol">.</span>Right <span class="symbol">:</span> SegmentPointConnections<span class="symbol">.</span>Bottom<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// calculate any intersecting points</span>
 currentSegment <span class="symbol">=</span> segment<span class="symbol">;</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span>Segment otherSegment <span class="keyword">in</span> segments<span class="symbol">.</span>Where<span class="symbol">(</span>s <span class="symbol">=&gt;</span> s <span class="symbol">!=</span> currentSegment<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Point intersection<span class="symbol">;</span>

 intersection <span class="symbol">=</span> Intersection<span class="symbol">.</span>FindLineIntersection<span class="symbol">(</span>segment<span class="symbol">.</span>Location<span class="symbol">,</span> segment<span class="symbol">.</span>EndLocation<span class="symbol">,</span> otherSegment<span class="symbol">.</span>Location<span class="symbol">,</span> otherSegment<span class="symbol">.</span>EndLocation<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>intersection<span class="symbol">.</span>IsEmpty<span class="symbol">)</span>
 <span class="symbol">{</span>
 SegmentPointConnections flags<span class="symbol">;</span>

 flags <span class="symbol">=</span> SegmentPointConnections<span class="symbol">.</span>None<span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>intersection <span class="symbol">!=</span> segment<span class="symbol">.</span>Location <span class="symbol">&amp;&amp;</span> intersection <span class="symbol">!=</span> segment<span class="symbol">.</span>EndLocation<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>segment<span class="symbol">.</span>Orientation <span class="symbol">==</span> SegmentOrientation<span class="symbol">.</span>Horizontal<span class="symbol">)</span>
 flags <span class="symbol">|=</span> <span class="symbol">(</span> SegmentPointConnections<span class="symbol">.</span>Left <span class="symbol">|</span> SegmentPointConnections<span class="symbol">.</span>Right<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 flags <span class="symbol">|=</span> <span class="symbol">(</span>SegmentPointConnections<span class="symbol">.</span>Top <span class="symbol">|</span> SegmentPointConnections<span class="symbol">.</span>Bottom<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>intersection <span class="symbol">!=</span> otherSegment<span class="symbol">.</span>Location <span class="symbol">&amp;&amp;</span> intersection <span class="symbol">!=</span> otherSegment<span class="symbol">.</span>EndLocation<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>otherSegment<span class="symbol">.</span>Orientation <span class="symbol">==</span> SegmentOrientation<span class="symbol">.</span>Horizontal<span class="symbol">)</span>
 flags <span class="symbol">|=</span> <span class="symbol">(</span>SegmentPointConnections<span class="symbol">.</span>Left <span class="symbol">|</span> SegmentPointConnections<span class="symbol">.</span>Right<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 flags <span class="symbol">|=</span> <span class="symbol">(</span>SegmentPointConnections<span class="symbol">.</span>Top <span class="symbol">|</span> SegmentPointConnections<span class="symbol">.</span>Bottom<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>flags <span class="symbol">!=</span> SegmentPointConnections<span class="symbol">.</span>None<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>UpdatePoint<span class="symbol">(</span>intersection<span class="symbol">,</span> flags<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Breaking the code down, we do the following:</p>
<ul>
<li>Create an additional four segments representing the boundaries
of the canvas</li>
<li>Sort the segments by their starting locations</li>
<li>Cycle each segment and</li>
<li>Create a point for the starting co-ordinate</li>
<li>Create a point for the ending co-ordinate</li>
<li>Cycle each other segment and see if it intersects with the
current segment. If it does, create a new point at the
intersection</li>
</ul>
<p>In any case above where I state to create a point, the code will
either create a point if one doesn't already exist, otherwise it
will update the connections of the existing point.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> UpdatePoint<span class="symbol">(</span>Point location<span class="symbol">,</span> SegmentPointConnections connections<span class="symbol">)</span>
<span class="symbol">{</span>
 SegmentPoint point<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>Points<span class="symbol">.</span>TryGetValue<span class="symbol">(</span>location<span class="symbol">,</span> <span class="keyword">out</span> point<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 point <span class="symbol">=</span> <span class="keyword">new</span> SegmentPoint <span class="symbol">{</span> Location <span class="symbol">=</span> location<span class="symbol">,</span> Connections <span class="symbol">=</span> connections <span class="symbol">}</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Points<span class="symbol">.</span>Add<span class="symbol">(</span>point<span class="symbol">.</span>Location<span class="symbol">,</span> point<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>point<span class="symbol">.</span>Connections<span class="symbol">.</span>HasFlag<span class="symbol">(</span>connections<span class="symbol">)</span><span class="symbol">)</span>
 point<span class="symbol">.</span>Connections <span class="symbol">|=</span> connections<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>CalculatePoints</code> method above is very inefficient, but it
does the job. Once this routine has run, we'll have an array of
co-ordinates and the directions of linked points.</p>
<h2 id="calculating-rectangles">Calculating Rectangles</h2>
<p>Now that we have all points, both user defined, and
intersections, we can use this to generate the actual
rectangles.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> CalculateRectangles<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 SegmentPoint<span class="symbol">[</span><span class="symbol">]</span> horizontalPoints<span class="symbol">;</span>
 SegmentPoint<span class="symbol">[</span><span class="symbol">]</span> verticalPoints<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Rectangles <span class="symbol">=</span> <span class="keyword">new</span> HashSet<span class="symbol">&lt;</span>Rectangle<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 horizontalPoints <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Points<span class="symbol">.</span>Values<span class="symbol">.</span>OrderBy<span class="symbol">(</span>p <span class="symbol">=&gt;</span> p<span class="symbol">.</span>X<span class="symbol">)</span><span class="symbol">.</span>ToArray<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 verticalPoints <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Points<span class="symbol">.</span>Values<span class="symbol">.</span>OrderBy<span class="symbol">(</span>p <span class="symbol">=&gt;</span> p<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">.</span>ToArray<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">foreach</span> <span class="symbol">(</span>SegmentPoint topLeft <span class="keyword">in</span> <span class="keyword">this</span><span class="symbol">.</span>Points<span class="symbol">.</span>Values<span class="symbol">.</span>Where<span class="symbol">(</span>p <span class="symbol">=&gt;</span> p<span class="symbol">.</span>Connections<span class="symbol">.</span>HasFlag<span class="symbol">(</span>SegmentPointConnections<span class="symbol">.</span>Left <span class="symbol">|</span> SegmentPointConnections<span class="symbol">.</span>Top<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 SegmentPoint topRight<span class="symbol">;</span>
 SegmentPoint bottomLeft<span class="symbol">;</span>

 topRight <span class="symbol">=</span> horizontalPoints<span class="symbol">.</span>FirstOrDefault<span class="symbol">(</span>p <span class="symbol">=&gt;</span> p<span class="symbol">.</span>X <span class="symbol">&gt;</span> topLeft<span class="symbol">.</span>X <span class="symbol">&amp;&amp;</span> p<span class="symbol">.</span>Y <span class="symbol">==</span> topLeft<span class="symbol">.</span>Y <span class="symbol">&amp;&amp;</span> p<span class="symbol">.</span>Connections<span class="symbol">.</span>HasFlag<span class="symbol">(</span>SegmentPointConnections<span class="symbol">.</span>Right <span class="symbol">|</span> SegmentPointConnections<span class="symbol">.</span>Top<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 bottomLeft <span class="symbol">=</span> verticalPoints<span class="symbol">.</span>FirstOrDefault<span class="symbol">(</span>p <span class="symbol">=&gt;</span> p<span class="symbol">.</span>X <span class="symbol">==</span> topLeft<span class="symbol">.</span>X <span class="symbol">&amp;&amp;</span> p<span class="symbol">.</span>Y <span class="symbol">&gt;</span> topLeft<span class="symbol">.</span>Y <span class="symbol">&amp;&amp;</span> p<span class="symbol">.</span>Connections<span class="symbol">.</span>HasFlag<span class="symbol">(</span>SegmentPointConnections<span class="symbol">.</span>Left <span class="symbol">|</span> SegmentPointConnections<span class="symbol">.</span>Bottom<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>topRight <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> bottomLeft <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 SegmentPoint bottomRight<span class="symbol">;</span>

 bottomRight <span class="symbol">=</span> horizontalPoints<span class="symbol">.</span>FirstOrDefault<span class="symbol">(</span>p <span class="symbol">=&gt;</span> p<span class="symbol">.</span>X <span class="symbol">==</span> topRight<span class="symbol">.</span>X <span class="symbol">&amp;&amp;</span> p<span class="symbol">.</span>Y <span class="symbol">==</span> bottomLeft<span class="symbol">.</span>Y <span class="symbol">&amp;&amp;</span> p<span class="symbol">.</span>Connections<span class="symbol">.</span>HasFlag<span class="symbol">(</span>SegmentPointConnections<span class="symbol">.</span>Right <span class="symbol">|</span> SegmentPointConnections<span class="symbol">.</span>Bottom<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>bottomRight <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle rectangle<span class="symbol">;</span>

 rectangle <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>topLeft<span class="symbol">.</span>X<span class="symbol">,</span> topLeft<span class="symbol">.</span>Y<span class="symbol">,</span> bottomRight<span class="symbol">.</span>X <span class="symbol">-</span> topLeft<span class="symbol">.</span>X<span class="symbol">,</span> bottomRight<span class="symbol">.</span>Y <span class="symbol">-</span> topLeft<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Rectangles<span class="symbol">.</span>Add<span class="symbol">(</span>rectangle<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>In this method, we loop through all our defined points that have
connections in the upper left corner.</p>
<p>For each matching point, we then try and find points with the
following criteria</p>
<ul>
<li>has the same Y co-ordinate and a right or top connection. This
gives us the upper right corner.</li>
<li>has the same X co-ordinate and a left or bottom connection.
This gives us the lower left corner.</li>
<li>if we have both the above corners, we then try and find a
point that has the same X co-ordinate as the upper right
corner, the same Y co-ordinate as the lower left corner, and
right or bottom connections. This gives us the last corner,
lower right</li>
<li>if we have all four corners, and the rectangle. The use of a
<code>HashSet</code> ensures the same rectangle isn't added twice.</li>
</ul>
<p>And that's all there is to it. With these two routines, I can
now break up a rectangle into many smaller pieces just by
defining pairs of points.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/rectangle3.png" class="gallery" title="Another example of the programs output" ><img src="https://images.cyotek.com/image/thumbnail/devblog/rectangle3.png" alt="Another example of the programs output" decoding="async" loading="lazy" /></a><figcaption>Another example of the programs output</figcaption></figure><h2 id="closing-remarks">Closing Remarks</h2>
<p>There are a few things that I'm aware of that the code doesn't
do</p>
<ul>
<li>As mentioned (several times!) none of this code is
particularly efficient. The more segments you add, the slower
it will get. Gareth Rees posted a nice <a href="https://stackoverflow.com/a/13923149/148962" rel="external nofollow noopener">diagram</a> of what I
should be doing, and indeed his pointers help me get this code
working originally</li>
<li>It doesn't handle overlapping segments very well. It will
rerun the point generation for these, adding to the overall
time</li>
<li>Ordering of the output rectangles isn't always what you
expect, it jumps around a bit</li>
<li>The end coordinate must be equal to or greater than the start
(using the sample, providing a negative segment size would
trigger this bug)</li>
</ul>
<p>As always the source code for this sample can be downloaded from
the link below.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2013-02-10 - First published</li>
<li>2020-11-21 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/dividing-up-a-rectangle-based-on-pairs-of-points-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating an image viewer in C# Part 5: Selecting part of an imageurn:uuid:e2670884-1a55-450f-9831-417fe9e335852012-11-25T09:20:27Z2012-05-30T17:09:14Z<p>Part 4 of this series (by far the most popular article on
cyotek.com) was supposed to be the end, but recently I was asked
if was possible to select part of an image for saving it to a
file. After implementing the new functionality and lacking ideas
for a new post on other matters, here we are with a new part!</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox-5a.png" class="gallery" title="The demonstration program showing the selection functionality" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox-5a.png" alt="The demonstration program showing the selection functionality" decoding="async" loading="lazy" /></a><figcaption>The demonstration program showing the selection functionality</figcaption></figure><h2 id="getting-started">Getting Started</h2>
<p>If you aren't already familiar with the <code>ImageBox</code> component,
you may wish to view parts <a href="/post/creating-a-scrollable-and-zoomable-image-viewer-in-csharp-part-1">1</a>, <a href="/post/creating-a-scrollable-and-zoomable-image-viewer-in-csharp-part-2">2</a>, <a href="/post/creating-a-scrollable-and-zoomable-image-viewer-in-csharp-part-3">3</a> and <a href="/post/creating-a-scrollable-and-zoomable-image-viewer-in-csharp-part-4">4</a> for
the original background and specification of the control.</p>
<p>First thing is to add some new properties, along with backing
events. These are:</p>
<ul>
<li><code>SelectionMode</code> - Determines if selection is available within
the control</li>
<li><code>SelectionColor</code> - Primary color for drawing the selection
region</li>
<li><code>SelectionRegion</code> - The currently selected region.</li>
<li><code>LimitSelectionToImage</code> - This property allows you to control
if the selection region can be drawn outside the image
boundaries.</li>
<li><code>IsSelecting</code> - This property returns if a selection operation
is in progress</li>
</ul>
<p>If the <code>SelectionMode</code> property is set, then the <code>AutoPan</code> and
<code>AllowClickZoom</code> properties will both be set to <code>false</code> to avoid
conflicting actions.</p>
<p>We also need a couple of new events not directly tried to
properties.</p>
<ul>
<li><code>Selecting</code> - Occurs when the user starts to draw a selection
region and can be used to cancel the action.</li>
<li><code>Selected</code> - Occurs when the user completes drawing a
selection region</li>
</ul>
<p>These events are called when setting the <code>IsSelecting</code> property:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">bool</span> IsSelecting
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _isSelecting<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">protected</span> <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_isSelecting <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 CancelEventArgs args<span class="symbol">;</span>

 args <span class="symbol">=</span> <span class="keyword">new</span> CancelEventArgs<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>value<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnSelecting<span class="symbol">(</span>args<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnSelected<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>args<span class="symbol">.</span>Cancel<span class="symbol">)</span>
 _isSelecting <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="drawing-the-selection-highlight">Drawing the selection highlight</h2>
<p>Before adding support for defining the selection region, we'll
add the code to draw it - that way we'll know the code to define
the region works! To do this, we'll modify the existing
<code>OnPaint</code> override, and insert a call to a new method named
<code>DrawSelection</code>:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnPaint<span class="symbol">(</span>PaintEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">/* Snipped existing code for brevity */</span>

 <span class="comment">// draw the selection</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">!=</span> Rectangle<span class="symbol">.</span>Empty<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>DrawSelection<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnPaint<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>DrawSelection</code> method itself is very straightforward. First
it fills the region with a translucent variant of the
<code>SelectionColor</code> property, then draws a solid outline around
this. A clip region is also applied to avoid overwriting the
controls borders.</p>
<p>As with most of the methods and properties in the <code>ImageBox</code>
control, it has been marked as <code>virtual</code> to allow you to
override it and provide your own drawing implementation if
required, without needing to redraw all of the control.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> DrawSelection<span class="symbol">(</span>PaintEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 RectangleF rect<span class="symbol">;</span>

 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>SetClip<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>GetInsideViewPort<span class="symbol">(</span><span class="keyword">true</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 rect <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetOffsetRectangle<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Brush brush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span>Color<span class="symbol">.</span>FromArgb<span class="symbol">(</span><span class="number">128</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionColor<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>brush<span class="symbol">,</span> rect<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Pen pen <span class="symbol">=</span> <span class="keyword">new</span> Pen<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionColor<span class="symbol">)</span><span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawRectangle<span class="symbol">(</span>pen<span class="symbol">,</span> rect<span class="symbol">.</span>X<span class="symbol">,</span> rect<span class="symbol">.</span>Y<span class="symbol">,</span> rect<span class="symbol">.</span>Width<span class="symbol">,</span> rect<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>ResetClip<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>GetOffsetRectangle</code> method will be described a little
further down this article.</p>
<h2 id="defining-the-selection-region">Defining the selection region</h2>
<p>Currently the selection region can only be defined via the
mouse; there is no keyboard support. To do this, we'll do the
usual overriding of <code>MouseDown</code>, <code>MouseMove</code> and <code>MouseUp</code></p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseDown<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseDown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">/* Snipped existing code for brevity */</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionMode <span class="symbol">!=</span> ImageBoxSelectionMode<span class="symbol">.</span>None<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Empty<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseMove<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseMove<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">/* Snipped existing code for brevity */</span>
 
 <span class="keyword">this</span><span class="symbol">.</span>ProcessSelection<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseUp<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseUp<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsPanning<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsPanning <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsSelecting<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsSelecting <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p><code>OnMouseDown</code> and <code>OnMouseUp</code> aren't being used for much in this
case, the former is used to clear an existing selection region,
the later to notify that the selection is no longer being
defined. <code>OnMouseMove</code> calls the <code>ProcessSelection</code> method which
is where all the action happens.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> ProcessSelection<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionMode <span class="symbol">!=</span> ImageBoxSelectionMode<span class="symbol">.</span>None<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>IsSelecting<span class="symbol">)</span>
 <span class="symbol">{</span>
 _startMousePosition <span class="symbol">=</span> e<span class="symbol">.</span>Location<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsSelecting <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>
</pre>
</figure>
<p>First, we check to make sure a valid selection mode is set.
Then, if a selection operation hasn't been initiated, we attempt
to set the <code>IsSelecting</code> property. As noted above, this property
will call the <code>Selecting</code> event allowing the selection to be
cancelled if required by the implementing application.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsSelecting<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">float</span> x<span class="symbol">;</span>
 <span class="keyword">float</span> y<span class="symbol">;</span>
 <span class="keyword">float</span> w<span class="symbol">;</span>
 <span class="keyword">float</span> h<span class="symbol">;</span>
 Point imageOffset<span class="symbol">;</span>

 imageOffset <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetImageViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">.</span>Location<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>X <span class="symbol">&lt;</span> _startMousePosition<span class="symbol">.</span>X<span class="symbol">)</span>
 <span class="symbol">{</span>
 x <span class="symbol">=</span> e<span class="symbol">.</span>X<span class="symbol">;</span>
 w <span class="symbol">=</span> _startMousePosition<span class="symbol">.</span>X <span class="symbol">-</span> e<span class="symbol">.</span>X<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 x <span class="symbol">=</span> _startMousePosition<span class="symbol">.</span>X<span class="symbol">;</span>
 w <span class="symbol">=</span> e<span class="symbol">.</span>X <span class="symbol">-</span> _startMousePosition<span class="symbol">.</span>X<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Y <span class="symbol">&lt;</span> _startMousePosition<span class="symbol">.</span>Y<span class="symbol">)</span>
 <span class="symbol">{</span>
 y <span class="symbol">=</span> e<span class="symbol">.</span>Y<span class="symbol">;</span>
 h <span class="symbol">=</span> _startMousePosition<span class="symbol">.</span>Y <span class="symbol">-</span> e<span class="symbol">.</span>Y<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 y <span class="symbol">=</span> _startMousePosition<span class="symbol">.</span>Y<span class="symbol">;</span>
 h <span class="symbol">=</span> e<span class="symbol">.</span>Y <span class="symbol">-</span> _startMousePosition<span class="symbol">.</span>Y<span class="symbol">;</span>
 <span class="symbol">}</span>

 x <span class="symbol">=</span> x <span class="symbol">-</span> imageOffset<span class="symbol">.</span>X <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>X<span class="symbol">;</span>
 y <span class="symbol">=</span> y <span class="symbol">-</span> imageOffset<span class="symbol">.</span>Y <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>Y<span class="symbol">;</span>
</pre>
</figure>
<p>If selection was allowed, we construct the co-ordinates for a
rectangle, automatically switching values around to ensure that
the rectangle will always have a positive width and height.
We'll also offset the co-ordinates if the image has been
scrolled or if it has been centred (or both!).</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 x <span class="symbol">=</span> x <span class="symbol">/</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">;</span>
 y <span class="symbol">=</span> y <span class="symbol">/</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">;</span>
 w <span class="symbol">=</span> w <span class="symbol">/</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">;</span>
 h <span class="symbol">=</span> h <span class="symbol">/</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">;</span>
</pre>
</figure>
<p>As this is the <em>zoomable</em> scrolling image control, we also need
to rescale the rectangle according to the current zoom level.
This ensures the <code>SelectionRegion</code> property always returns a
rectangle that describes the selection at 100% zoom.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>LimitSelectionToImage<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>x <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span>
 x <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>y <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span>
 y <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>x <span class="symbol">+</span> w <span class="symbol">&gt;</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width<span class="symbol">)</span>
 w <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width <span class="symbol">-</span> x<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>y <span class="symbol">+</span> h <span class="symbol">&gt;</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height<span class="symbol">)</span>
 h <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height <span class="symbol">-</span> y<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> <span class="keyword">new</span> RectangleF<span class="symbol">(</span>x<span class="symbol">,</span> y<span class="symbol">,</span> w<span class="symbol">,</span> h<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The final step is to constrain the rectangle to the image size
if the <code>LimitSelectionToImage</code> property is set, before assigning
the final rectangle to the <code>SelectionRegion</code> property.</p>
<p>And that's pretty much all there is to it.</p>
<h2 id="scaling-and-offsetting">Scaling and offsetting</h2>
<p>When using the control in our own products, it's very rarely to
display a single image, but rather to display multiple items, be
it sprites in a sprite sheet or tiles in a map. These
implementations therefore often require the ability to get a
single item, for example to display hover effects. This can be
tricky with a control that scrolls, zooms and centres the image.
Rather than repeat <code>ZoomFactor</code> calculations (and worse
<code>AutoScrollPosition</code>) everywhere, we added a number of helper
methods named <code>GetOffset*</code> and <code>GetScaled*</code>. Calling these with
a &quot;normal&quot; value, will return that value repositioned and
rescaled according to the current state of the control. An
example of this is the <code>DrawSelection</code> method described above
which needs ensure the current selection region is rendered
correctly.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">virtual</span> RectangleF GetScaledRectangle<span class="symbol">(</span>RectangleF source<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">new</span> RectangleF
 <span class="symbol">(</span>
 <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>source<span class="symbol">.</span>Left <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">,</span>
 <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>source<span class="symbol">.</span>Top <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">,</span>
 <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>source<span class="symbol">.</span>Width <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">,</span>
 <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>source<span class="symbol">.</span>Height <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span>
 <span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">virtual</span> RectangleF GetOffsetRectangle<span class="symbol">(</span>RectangleF source<span class="symbol">)</span>
<span class="symbol">{</span>
 RectangleF viewport<span class="symbol">;</span>
 RectangleF scaled<span class="symbol">;</span>
 <span class="keyword">float</span> offsetX<span class="symbol">;</span>
 <span class="keyword">float</span> offsetY<span class="symbol">;</span>

 viewport <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetImageViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 scaled <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetScaledRectangle<span class="symbol">(</span>source<span class="symbol">)</span><span class="symbol">;</span>
 offsetX <span class="symbol">=</span> viewport<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>X<span class="symbol">;</span>
 offsetY <span class="symbol">=</span> viewport<span class="symbol">.</span>Top <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Top <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>Y<span class="symbol">;</span>

 <span class="keyword">return</span> <span class="keyword">new</span> RectangleF<span class="symbol">(</span><span class="keyword">new</span> PointF<span class="symbol">(</span>scaled<span class="symbol">.</span>Left <span class="symbol">+</span> offsetX<span class="symbol">,</span> scaled<span class="symbol">.</span>Top <span class="symbol">+</span> offsetY<span class="symbol">)</span><span class="symbol">,</span> scaled<span class="symbol">.</span>Size<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Versions of these methods exist for the following structures:</p>
<ul>
<li><code>Point</code></li>
<li><code>PointF</code></li>
<li><code>Size</code></li>
<li><code>SizeF</code></li>
<li><code>Rectangle</code></li>
<li><code>RectangleF</code></li>
</ul>
<p>These methods can come in extremely useful depending on how you
are using the control!</p>
<h2 id="cropping-an-image">Cropping an image</h2>
<p>The demonstration program displays two <code>ImageBox</code> controls, the
first allows you to select part of an image, and the second
displays the cropped selection. I didn't add any sort of crop
functionality to the control itself, but the following snippets
shows how the demonstration program creates the cropped version.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
Rectangle rect<span class="symbol">;</span>

<span class="keyword">if</span> <span class="symbol">(</span>_previewImage <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 _previewImage<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

rect <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>imageBox<span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>X<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>imageBox<span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Y<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>imageBox<span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Width<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>imageBox<span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

_previewImage <span class="symbol">=</span> <span class="keyword">new</span> Bitmap<span class="symbol">(</span>rect<span class="symbol">.</span>Width<span class="symbol">,</span> rect<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">using</span> <span class="symbol">(</span>Graphics g <span class="symbol">=</span> Graphics<span class="symbol">.</span>FromImage<span class="symbol">(</span>_previewImage<span class="symbol">)</span><span class="symbol">)</span>
 g<span class="symbol">.</span>DrawImage<span class="symbol">(</span>imageBox<span class="symbol">.</span>Image<span class="symbol">,</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>Point<span class="symbol">.</span>Empty<span class="symbol">,</span> rect<span class="symbol">.</span>Size<span class="symbol">)</span><span class="symbol">,</span> rect<span class="symbol">,</span> GraphicsUnit<span class="symbol">.</span>Pixel<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

previewImageBox<span class="symbol">.</span>Image <span class="symbol">=</span> _previewImage<span class="symbol">;</span>
</pre>
</figure>
<h2 id="finishing-touches">Finishing touches</h2>
<p>We'll finish off by adding a couple of helper methods that
implementers can call:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">void</span> SelectAll<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException<span class="symbol">(</span><span class="string">&quot;No image set&quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> <span class="keyword">new</span> RectangleF<span class="symbol">(</span>PointF<span class="symbol">.</span>Empty<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Size<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">void</span> SelectNone<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> RectangleF<span class="symbol">.</span>Empty<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="known-issues">Known issues</h2>
<p>Currently, if you try and draw the selection bigger than the
visible area of the control, it will work, but it will not
scroll the control for you. I also was going to add the ability
to move or modify the selection but ran out of time for this
particular post.</p>
<p>As always, if you have any comments or questions, please contact
us!</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2012-05-30 - First published</li>
<li>2020-11-21 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/creating-an-image-viewer-in-csharp-part-5-selecting-part-of-an-image .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com