Introduction
I love pictures, as some of my articles can attest to. I have just about done everything I could with images (drawing them, giving them special filter effects, manipulated them to become desktop wallpapers, and so on), or so I thought.
It is possible to obtain certain information from your pictures. Some information you can obtain include: Equipment maker and model of the device that took the picture, or created the picture, was the flash used, Aperture, and Geolocation to name a few. This information is called EXIF data.
EXIF Data
EXIF (EXchangeable Image File format) is a standard that specifies the formats for images, sound, and ancillary tags used by digital cameras, smartphones, and scanners. The EXIF tag structure is borrowed from TIFF files.
Practical
Create a new Visual Basic.NET Windows Forms application and design it as shown in Figure 1.
Figure 1: Design
The Design simply includes a PictureBox, OpenFileDialog, and a Button. The Button will spawn the Open File DialogBox, allowing you to select an Image file. The Image then will be displayed inside the PictureBox.
Code
Add a new Class to your project, name it EXIFData, and implement the IDisposable Interface.
Public Class EXIFData Implements IDisposable
Add the fields.
Private bmp As Bitmap Private enc As System.Text.Encoding = Text.Encoding.UTF8
Bmp represents the Bitmap object to be read. Enc represents the Encoding of your Image. Now, for the Enums.
Public Enum PicStart TopLeft = 1 TopRight = 2 BottomRight = 3 BottomLeft = 4 LeftTop = 5 RightTop = 6 RightBottom = 7 LftBottom = 8 End Enum Public Enum EXIFExposures Manual = 1 Normal = 2 AperturePriority = 3 ShutterPriority = 4 Creative = 5 Action = 6 Portrait = 7 Landscape = 8 End Enum Public Enum EXIFExposureModes Unknown = 0 Average = 1 CenterWeightedAverage = 2 Spot = 3 MultiSpot = 4 MultiSegment = 5 [Partial] = 6 Other = 255 End Enum Public Enum Flash NotFired = 0 Fired = 1 FiredButNoStrobeReturned = 5 FiredAndStrobeReturned = 7 End Enum Public Enum LightSources Unknown = 0 Daylight = 1 Fluorescent = 2 Tungsten = 3 Flash = 10 StandardLightA = 17 StandardLightB = 18 StandardLightC = 19 D55 = 20 D65 = 21 D75 = 22 Other = 255 End Enum Public Enum EXIFDataTypes As Short UnsignedByte = 1 AsciiString = 2 UnsignedShort = 3 UnsignedLong = 4 UnsignedRational = 5 SignedByte = 6 Undefined = 7 SignedShort = 8 SignedLong = 9 SignedRational = 10 SingleFloat = 11 DoubleFloat = 12 End Enum
These Enumerations enable you to determine the Picture’s starting point, types of exposure options, and Flash settings. The next Enumeration you are going to add next contains all the tags that can exist in a picture. I have chosen to include all them in here so that you can see for yourself all the properties we can get from just one picture.
Public Enum EXIFTags As Integer ExifIFD = &H8769 GpsIFD = &H8825 NewSubfileType = &HFE SubfileType = &HFF ImageWidth = &H100 ImageHeight = &H101 BitsPerSample = &H102 Compression = &H103 PhotometricInterp = &H106 ThreshHolding = &H107 CellWidth = &H108 CellHeight = &H109 FillOrder = &H10A DocumentName = &H10D ImageDescription = &H10E EquipMake = &H10F EquipModel = &H110 StripOffsets = &H111 Orientation = &H112 SamplesPerPixel = &H115 RowsPerStrip = &H116 StripBytesCount = &H117 MinSampleValue = &H118 MaxSampleValue = &H119 XResolution = &H11A YResolution = &H11B PlanarConfig = &H11C PageName = &H11D XPosition = &H11E YPosition = &H11F FreeOffset = &H120 FreeByteCounts = &H121 GrayResponseUnit = &H122 GrayResponseCurve = &H123 T4Option = &H124 T6Option = &H125 ResolutionUnit = &H128 PageNumber = &H129 TransferFunction = &H12D SoftwareUsed = &H131 DateTime = &H132 Artist = &H13B HostComputer = &H13C Predictor = &H13D WhitePoint = &H13E PrimaryChromaticities = &H13F ColorMap = &H140 HalftoneHints = &H141 TileWidth = &H142 TileLength = &H143 TileOffset = &H144 TileByteCounts = &H145 InkSet = &H14C InkNames = &H14D NumberOfInks = &H14E DotRange = &H150 TargetPrinter = &H151 ExtraSamples = &H152 SampleFormat = &H153 SMinSampleValue = &H154 SMaxSampleValue = &H155 TransferRange = &H156 JPEGProc = &H200 JPEGInterFormat = &H201 JPEGInterLength = &H202 JPEGRestartInterval = &H203 JPEGLosslessPredictors = &H205 JPEGPointTransforms = &H206 JPEGQTables = &H207 JPEGDCTables = &H208 JPEGACTables = &H209 YCbCrCoefficients = &H211 YCbCrSubsampling = &H212 YCbCrPositioning = &H213 REFBlackWhite = &H214 ICCProfile = &H8773 Gamma = &H301 ICCProfileDescriptor = &H302 SRGBRenderingIntent = &H303 ImageTitle = &H320 Copyright = &H8298 ResolutionXUnit = &H5001 ResolutionYUnit = &H5002 ResolutionXLengthUnit = &H5003 ResolutionYLengthUnit = &H5004 PrintFlags = &H5005 PrintFlagsVersion = &H5006 PrintFlagsCrop = &H5007 PrintFlagsBleedWidth = &H5008 PrintFlagsBleedWidthScale = &H5009 HalftoneLPI = &H500A HalftoneLPIUnit = &H500B HalftoneDegree = &H500C HalftoneShape = &H500D HalftoneMisc = &H500E HalftoneScreen = &H500F JPEGQuality = &H5010 GridSize = &H5011 ThumbnailFormat = &H5012 ThumbnailWidth = &H5013 ThumbnailHeight = &H5014 ThumbnailColorDepth = &H5015 ThumbnailPlanes = &H5016 ThumbnailRawBytes = &H5017 ThumbnailSize = &H5018 ThumbnailCompressedSize = &H5019 ColorTransferFunction = &H501A ThumbnailData = &H501B ThumbnailImageWidth = &H5020 ThumbnailImageHeight = &H502 ThumbnailBitsPerSample = &H5022 ThumbnailCompression = &H5023 ThumbnailPhotometricInterp = &H5024 ThumbnailImageDescription = &H5025 ThumbnailEquipMake = &H5026 ThumbnailEquipModel = &H5027 ThumbnailStripOffsets = &H5028 ThumbnailOrientation = &H5029 ThumbnailSamplesPerPixel = &H502A ThumbnailRowsPerStrip = &H502B ThumbnailStripBytesCount = &H502C ThumbnailResolutionX = &H502D ThumbnailResolutionY = &H502E ThumbnailPlanarConfig = &H502F ThumbnailResolutionUnit = &H5030 ThumbnailTransferFunction = &H5031 ThumbnailSoftwareUsed = &H5032 ThumbnailDateTime = &H5033 ThumbnailArtist = &H5034 ThumbnailWhitePoint = &H5035 ThumbnailPrimaryChromaticities = &H5036 ThumbnailYCbCrCoefficients = &H5037 ThumbnailYCbCrSubsampling = &H5038 ThumbnailYCbCrPositioning = &H5039 ThumbnailRefBlackWhite = &H503A ThumbnailCopyRight = &H503B LuminanceTable = &H5090 ChrominanceTable = &H5091 FrameDelay = &H5100 LoopCount = &H5101 PixelUnit = &H5110 PixelPerUnitX = &H5111 PixelPerUnitY = &H5112 PaletteHistogram = &H5113 ExifExposureTime = &H829A ExifFNumber = &H829D ExifExposureProg = &H8822 ExifSpectralSense = &H8824 ExifISOSpeed = &H8827 ExifOECF = &H8828 ExifVer = &H9000 ExifDTOrig = &H9003 ExifDTDigitized = &H9004 ExifCompConfig = &H9101 ExifCompBPP = &H9102 ExifShutterSpeed = &H9201 ExifAperture = &H9202 ExifBrightness = &H9203 ExifExposureBias = &H9204 ExifMaxAperture = &H9205 ExifSubjectDist = &H9206 ExifMeteringMode = &H9207 ExifLightSource = &H9208 ExifFlash = &H9209 ExifFocalLength = &H920A ExifMakerNote = &H927C ExifUserComment = &H9286 ExifDTSubsec = &H9290 ExifDTOrigSS = &H9291 ExifDTDigSS = &H9292 ExifFPXVer = &HA000 ExifColorSpace = &HA001 ExifPixXDim = &HA002 ExifPixYDim = &HA003 ExifRelatedWav = &HA004 ExifInterop = &HA005 ExifFlashEnergy = &HA20B ExifSpatialFR = &HA20C ExifFocalXRes = &HA20E ExifFocalYRes = &HA20F ExifFocalResUnit = &HA210 ExifSubjectLoc = &HA214 ExifExposureIndex = &HA215 ExifSensingMethod = &HA217 ExifFileSource = &HA300 ExifSceneType = &HA301 ExifCfaPattern = &HA302 GpsVer = &H0 GpsLatitudeRef = &H1 GpsLatitude = &H2 GpsLongitudeRef = &H3 GpsLongitude = &H4 GpsAltitudeRef = &H5 GpsAltitude = &H6 GpsGpsTime = &H7 GpsGpsSatellites = &H8 GpsGpsStatus = &H9 GpsGpsMeasureMode = &HA GpsGpsDop = &HB GpsSpeedRef = &HC GpsSpeed = &HD GpsTrackRef = &HE GpsTrack = &HF GpsImgDirRef = &H10 GpsImgDir = &H11 GpsMapDatum = &H12 GpsDestLatRef = &H13 GpsDestLat = &H14 GpsDestLongRef = &H15 GpsDestLong = &H16 GpsDestBearRef = &H17 GpsDestBear = &H18 GpsDestDistRef = &H19 GpsDestDist = &H1A End Enum
To make proper use of these Enumerations, let us add the associated Properties which can be called from the Form.
Public Property Encoding() As System.Text.Encoding Get Return enc End Get Set(ByVal Value As System.Text.Encoding) If Not Value Is Nothing Then enc = Encoding End If End Set End Property Public ReadOnly Property EquipmentMake() As String Get Return GetPropString(EXIFTags.EquipMake) End Get End Property Public ReadOnly Property EquipmentModel() As String Get Return GetPropString(EXIFTags.EquipModel) End Get End Property Public ReadOnly Property Soft() As String Get Return GetPropString(EXIFTags.SoftwareUsed) End Get End Property Public ReadOnly Property Start() As PicStart Get Dim i As Int32 = GetPropInt16(EXIFTags.Orientation) If Not [Enum].IsDefined(GetType(PicStart), i) Then Return PicStart.TopLeft Else Return CType([Enum].Parse(GetType(PicStart), _ [Enum].GetName(GetType(PicStart), i)), PicStart) End If End Get End Property Public Property LastModified() As DateTime Get Try Return DateTime.ParseExact(GetPropString(EXIFTags _ .DateTime), "yyyy\:MM\:dd HH\:mm\:ss", Nothing) Catch ex As Exception Return DateTime.MinValue End Try End Get Set(ByVal Value As DateTime) Try SetPropString(EXIFTags.DateTime, _ Value.ToString("yyyy\:MM\:dd HH\:mm\:ss")) Catch ex As Exception End Try End Set End Property Public Property OriginalDate() As DateTime Get Try Return DateTime.ParseExact(GetPropString(EXIFTags _ .ExifDTOrig), "yyyy\:MM\:dd HH\:mm\:ss", Nothing) Catch ex As Exception Return DateTime.MinValue End Try End Get Set(ByVal Value As DateTime) Try SetPropString(EXIFTags.ExifDTOrig, _ Value.ToString("yyyy\:MM\:dd HH\:mm\:ss")) Catch ex As Exception End Try End Set End Property Public Property DigitizedDate() As DateTime Get Try Return DateTime.ParseExact(GetPropString(EXIFTags _ .ExifDTDigitized), "yyyy\:MM\:dd HH\:mm\:ss", _ Nothing) Catch ex As Exception Return DateTime.MinValue End Try End Get Set(ByVal Value As DateTime) Try SetPropString(EXIFTags.ExifDTDigitized, _ Value.ToString("yyyy\:MM\:dd HH\:mm\:ss")) Catch ex As Exception End Try End Set End Property Public ReadOnly Property Width() As Int32 Get Return Me.bmp.Width End Get End Property Public ReadOnly Property Height() As Int32 Get Return Me.bmp.Height End Get End Property Public ReadOnly Property XRes() As Double Get Dim i As Double = GetPropRational(EXIFTags.XResolution) _ .ToDouble() If GetPropInt16(EXIFTags.ResolutionUnit) = 3 Then 'CM' Return i * 2.54 Else Return i End If End Get End Property Public ReadOnly Property YRes() As Double Get Dim i As Double = GetPropRational(EXIFTags.YResolution) _ .ToDouble() If GetPropInt16(EXIFTags.ResolutionUnit) = 3 Then Return i * 2.54 Else Return i End If End Get End Property Public Property ImageTitle() As String Get Return GetPropString(EXIFTags.ImageTitle) End Get Set(ByVal Value As String) Try SetPropString(EXIFTags.ImageTitle, Value) Catch ex As Exception End Try End Set End Property Public Property Comment() As String Get Return GetPropString(EXIFTags.ExifUserComment) End Get Set(ByVal Value As String) Try SetPropString(EXIFTags.ExifUserComment, Value) Catch ex As Exception End Try End Set End Property Public Property ArtistName() As String Get Return GetPropString(EXIFTags.Artist) End Get Set(ByVal Value As String) Try SetPropString(EXIFTags.Artist, Value) Catch ex As Exception End Try End Set End Property Public Property ImageDescription() As String Get Return GetPropString(EXIFTags.ImageDescription) End Get Set(ByVal Value As String) Try SetPropString(EXIFTags.ImageDescription, Value) Catch ex As Exception End Try End Set End Property Public Property Copyright() As String Get Return GetPropString(EXIFTags.Copyright) End Get Set(ByVal Value As String) Try SetPropString(EXIFTags.Copyright, Value.ToString) Catch ex As Exception End Try End Set End Property Public ReadOnly Property ExposureTime() As Double Get If IsPropDefined(EXIFTags.ExifExposureTime) Then Return GetPropRational(EXIFTags.ExifExposureTime) _ .ToDouble ElseIf IsPropDefined(EXIFTags.ExifShutterSpeed) Then Return 1 / (2 ^ GetPropRational(EXIFTags _ .ExifShutterSpeed).ToDouble) Else Return 0 End If End Get End Property Public ReadOnly Property Aperture() As Double Get If IsPropDefined(EXIFTags.ExifFNumber) Then Return GetPropRational(EXIFTags.ExifFNumber).ToDouble() ElseIf IsPropDefined(EXIFTags.ExifAperture) Then Return Math.Sqrt(2) ^ GetPropRational(EXIFTags _ .ExifAperture).ToDouble() Else Return 0 End If End Get End Property Public ReadOnly Property ExposureProgram() As EXIFExposures Get Dim i As Int32 = GetPropInt16(EXIFTags.ExifExposureProg) If [Enum].IsDefined(GetType(EXIFExposures), i) Then Return CType([Enum].Parse(GetType(EXIFExposures), _ [Enum].GetName(GetType(EXIFExposures), i)), _ EXIFExposures) Else Return EXIFExposures.Normal End If End Get End Property Public ReadOnly Property ISOSensitivity() As Int16 Get Return GetPropInt16(EXIFTags.ExifISOSpeed) End Get End Property Public ReadOnly Property SubjectDistance() As Double Get Return GetPropRational(EXIFTags.ExifSubjectDist) _ .ToDouble() End Get End Property Public ReadOnly Property ExposureMeteringMode() As _ EXIFExposureModes Get Dim i As Int32 = GetPropInt16(EXIFTags.ExifMeteringMode) If [Enum].IsDefined(GetType(EXIFExposureModes), i) Then Return CType([Enum].Parse(GetType(EXIFExposureModes), _ [Enum].GetName(GetType(EXIFExposureModes), i)), _ EXIFExposureModes) Else Return EXIFExposureModes.Unknown End If End Get End Property Public ReadOnly Property FocalLength() As Double Get Return GetPropRational(EXIFTags.ExifFocalLength) _ .ToDouble End Get End Property Public ReadOnly Property FlashMode() As Flash Get Dim i As Int32 = GetPropInt16(EXIFTags.ExifFlash) If [Enum].IsDefined(GetType(Flash), i) Then Return CType([Enum].Parse(GetType(Flash), _ [Enum].GetName(GetType(Flash), i)), Flash) Else Return Flash.NotFired End If End Get End Property Public ReadOnly Property LightSource() As LightSources Get Dim X As Int32 = GetPropInt16(EXIFTags.ExifLightSource) If [Enum].IsDefined(GetType(LightSources), X) Then Return CType([Enum].Parse(GetType(LightSources), _ [Enum].GetName(GetType(LightSources), X)), _ LightSources) Else Return LightSources.Unknown End If End Get End Property
Add the Functions to interpret the EXIF data and display the output in a legible and understandable format.
Public Function GetBitmap() As Bitmap Return DirectCast(Me.bmp.Clone(), Bitmap) End Function Public Overrides Function ToString() As String Dim SB As New Text.StringBuilder SB.Append("Image:") SB.Append("\n\tDimensions: " & Width & " x " _ & Height & " px") SB.Append("\n\tResolution: " & XRes & " x " _ & YRes & " dpi") SB.Append("\n\tOrientation: " & _ [Enum].GetName(GetType(PicStart), Start)) SB.Append("\n\tTitle: " & ImageTitle) SB.Append("\n\tDescription: " & ImageDescription) SB.Append("\n\tCopyright: " & Copyright) SB.Append("\nEquipment:") SB.Append("\n\tMaker: " & EquipmentMake) SB.Append("\n\tModel: " & EquipmentModel) SB.Append("\n\tSoftware: " & Soft) SB.Append("\nDate and time:") SB.Append("\n\tGeneral: " & LastModified.ToString()) SB.Append("\n\tOriginal: " & OriginalDate.ToString()) SB.Append("\n\tDigitized: " & DigitizedDate.ToString()) SB.Append("\nShooting conditions:") SB.Append("\n\tExposure time: " & _ ExposureTime.ToString("N4") & " s") SB.Append("\n\tExposure program: " & _ [Enum].GetName(GetType(EXIFExposures), ExposureProgram)) SB.Append("\n\tExposure mode: " & _ [Enum].GetName(GetType(EXIFExposureModes), _ ExposureMeteringMode)) SB.Append("\n\tAperture: F" & _ Aperture.ToString("N2")) SB.Append("\n\tISO sensitivity: " & ISOSensitivity) SB.Append("\n\tSubject distance: " & _ SubjectDistance.ToString("N2") & " m") SB.Append("\n\tFocal length: " & FocalLength) SB.Append("\n\tFlash: " & _ [Enum].GetName(GetType(Flash), FlashMode)) SB.Append("\n\tLight source (WB):" & _ [Enum].GetName(GetType(LightSources), LightSource)) SB.Replace("\n", vbCrLf) SB.Replace("\t", vbTab) Return SB.ToString() End Function Public Function IsPropDefined(ByVal p As Int32) As Boolean Return CBool([Array].IndexOf(Me.bmp.PropertyIdList, p) > -1) End Function Public Function GetPropInt32(ByVal p As Int32, Optional ByVal _ val As Int32 = 0) As Int32 If IsPropDefined(p) Then Return GetInt32(Me.bmp.GetPropertyItem(p).Value) Else Return val End If End Function Public Function GetPropInt16(ByVal p As Int32, Optional ByVal _ val As Int16 = 0) As Int16 If IsPropDefined(p) Then Return GetInt16(Me.bmp.GetPropertyItem(p).Value) Else Return val End If End Function Public Function GetPropString(ByVal p As Int32, Optional ByVal _ val As String = "") As String If IsPropDefined(p) Then Return GetString(Me.bmp.GetPropertyItem(p).Value) Else Return val End If End Function Public Function GetProp(ByVal p As Int32, Optional ByVal val _ As Byte() = Nothing) As Byte() If IsPropDefined(p) Then Return Me.bmp.GetPropertyItem(p).Value Else Return val End If End Function Public Function GetPropRational(ByVal p As Int32) As _ EXIFRational If IsPropDefined(p) Then Return GetExifRational(Me.bmp.GetPropertyItem(p).Value) Else Dim i As EXIFRational i.iNumerator = 0 i.iDenominator = 1 Return i End If End Function Public Sub SetPropString(ByVal p As Int32, ByVal val As String) Dim Bytes() As Byte = enc.GetBytes(val & vbNullChar) SetProp(p, Bytes, EXIFDataTypes.AsciiString) End Sub Public Sub SetPropInt16(ByVal p As Int32, ByVal val As Int16) Dim Bytes(1) As Byte Bytes(0) = CType(val And &HFF, Byte) Bytes(1) = CType((val And &HFF00) >> 8, Byte) SetProp(p, Bytes, EXIFDataTypes.SignedShort) End Sub Public Sub SetPropInt32(ByVal p As Int32, ByVal val As Int32) Dim Bytes(3) As Byte For i As Int32 = 0 To 3 Bytes(i) = CType(val And &HFF, Byte) val >>= 8 Next SetProp(p, Bytes, EXIFDataTypes.SignedLong) End Sub Public Sub SetProp(ByVal p As Int32, ByVal Bytes() As Byte, _ ByVal Type As EXIFDataTypes) Dim PI As Imaging.PropertyItem = Me.bmp.PropertyItems(0) PI.Id = p PI.Value = Bytes PI.Type = Type PI.Len = Bytes.Length Me.bmp.SetPropertyItem(PI) End Sub Private Function GetInt32(ByVal Bt As Byte()) As Int32 Return Bt(3) << 24 Or Bt(2) << 16 Or Bt(1) << 8 Or Bt(0) End Function Private Function GetInt16(ByVal Bt As Byte()) As Int16 Return Bt(1) << 8 Or Bt(0) End Function Private Function GetString(ByVal Bt As Byte()) As String Dim s As String = enc.GetString(Bt) If s.EndsWith(vbNullChar) Then s = s.Substring(0, _ s.Length - 1) Return s End Function Private Function GetExifRational(ByVal B As Byte()) _ As EXIFRational Dim i As New EXIFRational, N(3), D(3) As Byte Array.Copy(B, 0, N, 0, 4) Array.Copy(B, 4, D, 0, 4) i.iDenominator = GetInt32(D) i.iNumerator = GetInt32(N) Return i End Function
Add the Class Initialization and Disposal.
Public Structure EXIFRational Dim iNumerator As Int32 Dim iDenominator As Int32 Shadows Function ToString(Optional ByVal Delimiter _ As String = "/") As String Return iNumerator & Delimiter & iDenominator End Function Function ToDouble() As Double Return iNumerator / iDenominator End Function End Structure Public Sub New(ByRef bmp As Bitmap) If Not bmp Is Nothing Then Me.bmp = bmp End If End Sub Public Sub New(ByVal strFileName As String) Me.bmp = DirectCast(Image.FromFile(strFileName), Bitmap) End Sub Public Sub Dispose() Implements IDisposable.Dispose Me.bmp.Dispose() End Sub
Add the following code to the Button’s Click event on the Form.
Private Sub btnOpen_Click(sender As Object, e As EventArgs) _ Handles btnOpen.Click Dim ofd As New OpenFileDialog ofd.Filter = "Bitmap|*.bmp|JPEG|*.jpg" If ofd.ShowDialog = Windows.Forms.DialogResult.Cancel Then _ Exit Sub Dim bmp As New Bitmap(ofd.FileName) If Not IsNothing(picImage.Image) Then _ picImage.Image.Dispose() picImage.Image = bmp Dim EW As New EXIFData(ofd.FileName) Console.WriteLine(EW.ToString()) EW.Dispose() End Sub
When a valid Picture has been selected, the Data output would be similar to the data shown in Listing 1.
Figure 2: Run time
Figure 2 is a picture I took in April of 2018 in the Kruger National Park in South Africa. Now, let’s look at the output.
Image: Dimensions: 4608 x 3456 px Resolution: 180 x 180 dpi Orientation: TopLeft Title: Description: Copyright: Equipment: Maker: Canon Model: Canon PowerShot A1400 Software: Date and time: General: 2018-03-31 08:03:05 AM Original: 2018-03-31 08:03:05 AM Digitized: 2018-03-31 08:03:05 AM Shooting conditions: Exposure time: 0.0063 s Exposure program: Normal Exposure mode: MultiSegment Aperture: F6.90 ISO sensitivity: 250 Subject distance: 0.00 m Focal length: 0.991489361702128 Flash: NotFired Light source (WB): Unknown
Listing 1: Output
Conclusion
I have only covered the tip of the iceberg with information you can obtain from pictures. Now, you have a lot of techniques you can explore further. I hope my article helped.