Cyotek Development Bloghttps://devblog.cyotek.com/tag/jpg/atom.xml2019-03-09T11:48:18ZHandling the orientation EXIF tag in images using C#urn:uuid:2462f9f8-d0d8-4bef-ae9c-dd83814f82c52019-03-09T11:48:18Z2019-03-09T11:12:10Z<p>Two weeks ago I received an interesting support ticket from a
user of our <a href="/tag/imagebox">ImageBox</a> control, stating that when being used
for <code>.png</code> files, the control was absolutely file but when used
with <code>.jpg</code> files that had been rotated using Windows Explorer
shell context menus, they were displayed in the original
orientation.</p>
<p>As soon as I read the ticket I had a hunch what was going to be
the ultimate cause, and was able to quickly reproduce the
problem and then confirm my hunch.</p>
<h2 id="introducing-exchangeable-image-file-format">Introducing exchangeable image file format</h2>
<p>JEPG images support extension attributes via the <strong>Exchangeable
image file format</strong> (<strong>Exif</strong>). These attributes can be used to
provide additional information about an image, such as how and
where it was captured. It can also store information such as the
software used to manipulate the image, artist and copyright
information, or camera model details. In other words, it's
similar to how you can add tags to an MP3 file with album
information, although usually this doesn't cause your MP3 file
to play backwards!</p>
<p>The Exif specification is available from the <a href="http://www.cipa.jp/index_e.html" rel="external nofollow noopener">CIPA</a> website,
although at 191
pages long it is a hefty read.</p>
<h2 id="introducing-the-orientation-attribute">Introducing the orientation attribute</h2>
<p>I mentioned that MP3 tags don't cause your MP3 files to play
backwards (at least I'm not aware of any that do!), but Exif
supports an orientation attribute that means the source image
has one orientation, but should be displayed in another in order
to appear correctly.</p>
<p>The tag supports one of the following 8 values</p>
<table>
<thead>
<tr>
<th>EXIF Orientation Value</th>
<th>Row #0 is:</th>
<th>Column #0 is:</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Top</td>
<td>Left side</td>
</tr>
<tr>
<td>2*</td>
<td>Top</td>
<td>Right side</td>
</tr>
<tr>
<td>3</td>
<td>Bottom</td>
<td>Right side</td>
</tr>
<tr>
<td>4*</td>
<td>Bottom</td>
<td>Left side</td>
</tr>
<tr>
<td>5*</td>
<td>Left side</td>
<td>Top</td>
</tr>
<tr>
<td>6</td>
<td>Right side</td>
<td>Top</td>
</tr>
<tr>
<td>7*</td>
<td>Right side</td>
<td>Bottom</td>
</tr>
<tr>
<td>8</td>
<td>Left side</td>
<td>Bottom</td>
</tr>
</tbody>
</table>
<p>The starred values also flip the image in addition to rotating
it.</p>
<p>Source: <a href="https://www.impulseadventure.com/photo/exif-orientation.html" rel="external nofollow noopener">ImpulseAdventure - JPEG / Exif Orientation and
Rotation</a></p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/exif-1a.png" class="gallery" title="An example of an uncommon orientation value" ><img src="https://images.cyotek.com/image/thumbnail/devblog/exif-1a.png" alt="An example of an uncommon orientation value" decoding="async" loading="lazy" /></a><figcaption>An example of an uncommon orientation value</figcaption></figure><h2 id="enter-windows-explorer">Enter Windows Explorer</h2>
<p>Windows Explorer has knowledge of the Exif orientation tag, and
automatically rotates thumbnail previews so they appear correct
- very helpful!</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/exif-1c.png" class="gallery" title="Windows Explorer showing correctly orientated images" ><img src="https://images.cyotek.com/image/thumbnail/devblog/exif-1c.png" alt="Windows Explorer showing correctly orientated images" decoding="async" loading="lazy" /></a><figcaption>Windows Explorer showing correctly orientated images</figcaption></figure>
<p>However, if you context click an image and make use of the
<strong>Rotate Left</strong> and <strong>Rotate Right</strong> commands, Explorer doesn't
truly rotate the images. Instead, for JEPG images, an
appropriate orientation attribute is added (plus a large block
of XML, it wouldn't be Microsoft if they weren't adding extra
unwanted data in files). This is quite clever in a way, as it
means it doesn't touch the original image data at all, but does
mean that any application that doesn't understand Exif is going
to display the image wrong.</p>
<p>Also somewhat annoyingly, when you view the properties of the
image, the <strong>Details</strong> tab includes lots of information gleaned
from Exif - but neglects to mention that a custom orientation is
in use.</p>
<h2 id="enter.net">Enter .NET</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/exif-1d.png" class="gallery" title="The top image is using the Image object directly after loading, the bottom image is the result of manually processing the orientation attribute" ><img src="https://images.cyotek.com/image/thumbnail/devblog/exif-1d.png" alt="The top image is using the Image object directly after loading, the bottom image is the result of manually processing the orientation attribute" decoding="async" loading="lazy" /></a><figcaption>The top image is using the Image object directly after loading, the bottom image is the result of manually processing the orientation attribute</figcaption></figure>
<p>When it comes to loading images in .NET, I usually use
<code>Image.FromFile</code> or <code>Image.FromStream</code> if I'm just loading
common formats. Unfortunately, these return the images &quot;as-is&quot;,
without doing any post processing. I said unfortunately, but
actually this is probably a good thing - most likely you don't
want .NET manipulating images for you.</p>
<h2 id="fixing-the-problem">Fixing the problem</h2>
<p>The <code>Image</code> class does provide access to Exif properties, albeit
in a very obsure way. <code>Image.PropertyIdList</code> returns an <code>int</code>
array of defined extension values, and <code>GetPropertyItem</code> will
return a <code>PropertyItem</code> instance describing an existing
property. You can then use the <code>Value</code> property to get the
attribute value, although this may require further conversion to
use (e.g. convert raw bytes into a string).</p>
<blockquote>
<p>Note: Calling <code>GetPropertyItem</code> will throw an
<code>ArgumentException</code> if you request a property that doesn't
exist. You should use <code>PropertyIdList</code> to check that the
property exists first.</p>
</blockquote>
<p>The following extension method (body code from <a href="https://stackoverflow.com/a/23400751/148962" rel="external nofollow noopener">this Stack
Overflow answer</a>) will take a source <code>Image</code>, check to see if
the orientation attribute is present and if it is rotate/flip
the image as appropriate and then remove the attribute.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> NormalizeOrientation<span class="symbol">(</span><span class="keyword">this</span> Image image<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>Array<span class="symbol">.</span>IndexOf<span class="symbol">(</span>image<span class="symbol">.</span>PropertyIdList<span class="symbol">,</span> ExifOrientationTagId<span class="symbol">)</span> <span class="symbol">&gt;</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> orientation<span class="symbol">;</span>

 orientation <span class="symbol">=</span> image<span class="symbol">.</span>GetPropertyItem<span class="symbol">(</span>ExifOrientationTagId<span class="symbol">)</span><span class="symbol">.</span>Value<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>orientation <span class="symbol">&gt;=</span> <span class="number">1</span> <span class="symbol">&amp;&amp;</span> orientation <span class="symbol">&lt;=</span> <span class="number">8</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">switch</span> <span class="symbol">(</span>orientation<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> <span class="number">2</span><span class="symbol">:</span>
 image<span class="symbol">.</span>RotateFlip<span class="symbol">(</span>RotateFlipType<span class="symbol">.</span>RotateNoneFlipX<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="number">3</span><span class="symbol">:</span>
 image<span class="symbol">.</span>RotateFlip<span class="symbol">(</span>RotateFlipType<span class="symbol">.</span>Rotate<span class="number">180</span>FlipNone<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="number">4</span><span class="symbol">:</span>
 image<span class="symbol">.</span>RotateFlip<span class="symbol">(</span>RotateFlipType<span class="symbol">.</span>Rotate<span class="number">180</span>FlipX<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="number">5</span><span class="symbol">:</span>
 image<span class="symbol">.</span>RotateFlip<span class="symbol">(</span>RotateFlipType<span class="symbol">.</span>Rotate<span class="number">90</span>FlipX<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="number">6</span><span class="symbol">:</span>
 image<span class="symbol">.</span>RotateFlip<span class="symbol">(</span>RotateFlipType<span class="symbol">.</span>Rotate<span class="number">90</span>FlipNone<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="number">7</span><span class="symbol">:</span>
 image<span class="symbol">.</span>RotateFlip<span class="symbol">(</span>RotateFlipType<span class="symbol">.</span>Rotate<span class="number">270</span>FlipX<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="number">8</span><span class="symbol">:</span>
 image<span class="symbol">.</span>RotateFlip<span class="symbol">(</span>RotateFlipType<span class="symbol">.</span>Rotate<span class="number">270</span>FlipNone<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 image<span class="symbol">.</span>RemovePropertyItem<span class="symbol">(</span>ExifOrientationTagId<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>If your application deals with user submitted images, having
something like this in your arsenal may be useful; I know I have
several applications that now need this &quot;fix&quot;.</p>
<h2 id="demonstration-program">Demonstration program</h2>
<p>The linked source code download includes a demonstration program
that features each of 8 supported orientation values and shows
how they are displayed both before and after processing the
orientation attribute.</p>
<h2 id="fixing-imagebox">Fixing ImageBox</h2>
<p>At this time I'm still mulling over if this is something I
should be handling within ImageBox, or if it is the
responsibility of the program using the control. Given ImageBox
typical use cases, I'm leaning towards the idea of adding a new
option to automatically handle this but am currently undecided.</p>
<h2 id="credits">Credits</h2>
<ul>
<li>Tower of London photograph used for sample images - <a href="https://www.publicdomainpictures.net/en/view-image.php?image=18490" rel="external nofollow noopener">Vera
Kratochvil</a></li>
<li>Information on Exif orientation - <a href="https://www.impulseadventure.com/photo/exif-orientation.html" rel="external nofollow noopener">ImpulseAdventure</a></li>
<li>Code for processing Exif orientation tags in C# - <a href="https://stackoverflow.com/a/23400751/148962" rel="external nofollow noopener">ReenignE</a></li>
</ul>
<h2 id="update-history">Update History</h2>
<ul>
<li>2019-03-09 - First published</li>
<li>2020-11-22 - 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/handling-the-orientation-exif-tag-in-images-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com