Read image with WPF

Read Pipeline Metadata TIFF Write Format Conversion WPF Stream

Demonstrates passing an image to a pipeline with the custom image reader.

Сode Snippet

public sealed class WpfImageReader : IImageReader, IImageParams
{
    private readonly BitmapSource bitmapSource;

    public WpfImageReader(System.IO.Stream stream)
    {
        this.bitmapSource = BitmapFrame.Create(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);

        if (this.bitmapSource.Metadata is BitmapMetadata metadata)
        {
            this.Exif = new ExifDictionary();
            this.CaptureMetadata(metadata, string.Empty);
        }
    }

    public Aurigma.GraphicsMill.Ink Ink => null;

    public ColorPalette Palette => null;

    public ColorProfile ColorProfile => null;

    public float DpiX
    {
        get
        {
            return (float)this.bitmapSource.DpiX;
        }
    }

    public float DpiY
    {
        get
        {
            return (float)this.bitmapSource.DpiY;
        }
    }

    public PixelFormat PixelFormat
    {
        get
        {
            return ConvertFormat(this.bitmapSource.Format);
        }
    }

    public int Height
    {
        get
        {
            return this.bitmapSource.PixelHeight;
        }
    }

    public int Width
    {
        get
        {
            return this.bitmapSource.PixelWidth;
        }
    }

    public ExifDictionary Exif { get; private set; }

    public ImageParams GetImageParams()
    {
        return ImageParams.Create(this);
    }

    public Bitmap GetStripe(int stripeY, int stripeHeight)
    {
        // In production, it makes sense to consider more sophisticated logic
        // for disposing stripe bitmaps. The possible approach is to dispose
        // the previous stripe with generating the next one.
        var stripe = new Bitmap(this.Width, stripeHeight, this.PixelFormat);

        stripe.DpiX = this.DpiX;
        stripe.DpiY = this.DpiY;

        this.bitmapSource.CopyPixels(new System.Windows.Int32Rect(0, stripeY, stripe.Width, stripe.Height), stripe.Scan0, stripe.Stride * stripe.Height, stripe.Stride);

        return stripe;
    }

    /// <summary>
    /// Converts WPF pixel format to corresponding one for Graphics Mill.
    /// </summary>
    private static PixelFormat ConvertFormat(System.Windows.Media.PixelFormat format)
    {
        var dict = new Dictionary<System.Windows.Media.PixelFormat, Aurigma.GraphicsMill.PixelFormat>
        {
            { System.Windows.Media.PixelFormats.Bgr24, Aurigma.GraphicsMill.PixelFormat.Format24bppRgb },
            { System.Windows.Media.PixelFormats.Bgr32, Aurigma.GraphicsMill.PixelFormat.Format32bppRgb },
            { System.Windows.Media.PixelFormats.Bgra32, Aurigma.GraphicsMill.PixelFormat.Format32bppArgb },
            { System.Windows.Media.PixelFormats.Gray8, Aurigma.GraphicsMill.PixelFormat.Format8bppGrayscale },
            { System.Windows.Media.PixelFormats.Gray16, Aurigma.GraphicsMill.PixelFormat.Format16bppGrayscale },
        };

        if (!dict.ContainsKey(format))
        {
            throw new Aurigma.GraphicsMill.UnsupportedPixelFormatException();
        }

        return dict[format];
    }

    private static UnsignedRational ToRational(ulong value)
    {
        var firstHalf = (uint)(value >> 32);
        var secondHalf = (uint)(value & 0xffffffff);

        return new UnsignedRational(firstHalf, secondHalf);
    }

    private static Rational ToRational(long value)
    {
        var firstHalf = (int)(value >> 32);
        var secondHalf = (int)(value & 0xffffffff);

        return new Rational(firstHalf, secondHalf);
    }

    private void WriteTag(long tag, object value)
    {
        if (this.Exif.Contains(tag))
        {
            return;
        }

        if (value is byte)
        {
            this.Exif.Add(tag, (byte)value);
        }
        else if (value is long)
        {
            this.Exif.Add(tag, ToRational((long)value));
        }
        else if (value is ulong)
        {
            this.Exif.Add(tag, ToRational((ulong)value));
        }
        else if (value is ushort)
        {
            this.Exif.Add(tag, (ushort)value);
        }
        else if (value is short)
        {
            this.Exif.Add(tag, (short)value);
        }
        else if (value is string)
        {
            this.Exif.Add(tag, value as string);
        }
        else if (value is ulong[])
        {
            var data = value as ulong[];
            this.Exif.Add(tag, new UnsignedRational[] { ToRational(data[0]), ToRational(data[1]), ToRational(data[2]) });
        }
        else if (value is long[])
        {
            var data = value as long[];
            this.Exif.Add(tag, new Rational[] { ToRational(data[0]), ToRational(data[1]), ToRational(data[2]) });
        }
        else
        {
            const long exifTagUserComment = 0x9286;
            if (tag == exifTagUserComment)
            {
                var blob = value as BitmapMetadataBlob;
                this.Exif.Add(tag, blob.GetBlobValue());
            }
        }
    }

    private void CaptureMetadata(BitmapMetadata metadata, string query)
    {
        foreach (var relativeQuery in metadata)
        {
            var queryReader = metadata.GetQuery(relativeQuery);

            if (queryReader is BitmapMetadata innerMetadata)
            {
                this.CaptureMetadata(innerMetadata, query + relativeQuery);
            }
            else if (query.Contains("app1") || query.Contains("ifd"))
            {
                var m = Regex.Match(relativeQuery, @"\d+");
                if (m.Length == 0)
                {
                    continue;
                }

                var tag = long.Parse(m.Value);
                WriteTag(tag, queryReader);
            }
        }
    }

    internal class ReadImageWithWPF
    {
        public static void Run()
        {
            using (var fs = new FileStream("IMG_4212.HEIC", FileMode.Open, FileAccess.Read))
            using (var reader = new CustomImageReader(new WpfImageReader(fs)))
            using (var writer = new TiffWriter("ReadImageWithWPF.tif"))
            {
                var wpfReader = (WpfImageReader)reader.InternalImageReader;
                if (wpfReader.Exif != null)
                {
                    writer.Exif = wpfReader.Exif;

                    foreach (object key in wpfReader.Exif.Keys)
                    {
                        Console.WriteLine("{0}: {1}, {2}", wpfReader.Exif.GetKeyDescription(key), wpfReader.Exif[key], wpfReader.Exif.GetItemString(key));
                    }
                }

                Pipeline.Run(reader + writer);
            }
        }
    }
}

Input

IMG_4212.HEIC

Download

Output

ReadImageWithWPF.tif

Download

ReadImageWithWPF.txt

Image input equipment manufacturer: Apple, Apple
Image input equipment model: iPhone 8, iPhone 8
Image resolution in width direction: 1/72, 1/72
Image resolution in height direction: 1/72, 1/72
Resolution unit: 2, inches
Software used: 15.4.1, 15.4.1
File change date and time: 2022:06:23 13:02:55, 2022:06:23 13:02:55
Exposure time: 1372/1, 1372/1
F number: 5/9, 5/9
Exposure program: 2, normal
ISO speed rating: 20, 20
Date and time of original data generation: 2022:06:23 13:02:55, 2022:06:23 13:02:55
Date and time of digital data generation: 2022:06:23 13:02:55, 2022:06:23 13:02:55
Shutter speed: 16693/173971, 16693/173971
Aperture: 32325/54823, 32325/54823
Brightness: 132492/1249693, 132492/1249693
Exposure bias: 1/0, 1/0
Metering mode: 5, pattern
Flash: 16, flash did not fire
Lens focal length: 100/399, 100/399
DateTimeOriginal subseconds: 274, 274
DateTimeDigitized subseconds: 274, 274
Color space information: 65535, uncalibrated
Sensing method: 2, one-chip color area sensor
Exposure mode: 0, auto exposure
White balance: 0, auto white balance
Focal length in 35 mm film: 28, 28
Latitude: 1/54, 0.0185185/0.0175439/0.0323625
East or West Longitude: E, E
Longitude: 1/20, 0.05/0.037037/0.0187793
Altitude reference: 0, sea level
Altitude: 32603/40692, 32603/40692
Speed unit: K, Km/h
Speed of Gps receiver: 17411/21253, 17411/21253
Reference for direction of image: T, true direction
Direction of image: 6445/649856, 6445/649856
Reference for bearing of destination: T, true direction
Bearing of destination: 6445/649856, 6445/649856
GPS date: 2022:06:23, 2022:06:23
GPS horizontal positioning errors in meters: 1/24, 1/24
Interoperability identification: N, N

For AI-assisted development: Download Graphics Mill Code Samples XML Catalog