private static async Task <Photo.Metadata> reloadAndSave(
            Photo photo, bool forceJpeg, string destFile)
        {
            using (var mmap = MemoryMappedFile.OpenExisting(
                       mmapName(photo.FileName),
                       MemoryMappedFileRights.Read)) {
                using (var stream = new UnsafeMemoryMapStream(
                           mmap.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read),
                           FileAccess.Read)) {
                    (var sourceFrame, var format) = loadFrame(stream.Stream);
                    var            md          = sourceFrame.Metadata.Clone() as BitmapMetadata;
                    Photo.Metadata newMetadata = await Exif.SetMetadata(photo, md);

                    newMetadata.Width  = sourceFrame.PixelWidth;
                    newMetadata.Height = sourceFrame.PixelHeight;
                    BitmapEncoder encoder = forceJpeg ?
                                            new JpegBitmapEncoder() :
                                            BitmapEncoder.Create(format);
                    encoder.Frames.Add(
                        BitmapFrame.Create(
                            sourceFrame,
                            null,
                            md,
                            sourceFrame.ColorContexts));
                    if (encoder is JpegBitmapEncoder jpg)
                    {
                        jpg.Rotation = Exif.SaveRotation(md);
                    }
                    sourceFrame = null;
                    using (var outFile = new FileStream(destFile, FileMode.CreateNew)) {
                        encoder.Save(outFile);
                    }
                    return(newMetadata);
                }
            }
        }
        private async Task loadMeta(Photo photo,
                                    ObservableCollection <Photo> list)
        {
            try {
                if (photo.Disposed)
                {
                    return;
                }
                var mmap = MemoryMappedFile.CreateFromFile(
                    File.Open(photo.FileName,
                              FileMode.Open,
                              FileAccess.Read,
                              FileShare.Delete | FileShare.Read),
                    mmapName(photo.FileName),
                    0,
                    MemoryMappedFileAccess.Read,
                    HandleInheritability.None,
                    false);
                var                 img = new BitmapImage();
                Photo.Metadata      metadata;
                DispatcherOperation metaSet;
                try {
                    using (var stream = new UnsafeMemoryMapStream(
                               mmap.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read),
                               FileAccess.Read)) {
                        var data = stream.Stream;
                        {
                            var decoder = BitmapDecoder.Create(data,
                                                               BitmapCreateOptions.PreservePixelFormat,
                                                               BitmapCacheOption.None);
                            var frames = decoder.Frames;
                            if (frames.Count < 1)
                            {
                                throw new ArgumentException("Image contained no frame data.", nameof(photo));
                            }
                            if (!(frames[0].Metadata is BitmapMetadata imgMeta))
                            {
                                throw new NullReferenceException("Image contained no metadata");
                            }
                            metadata        = Exif.GetMetadata(imgMeta);
                            metadata.Width  = frames[0].PixelWidth;
                            metadata.Height = frames[0].PixelHeight;
                        }

                        data.Seek(0, SeekOrigin.Begin);
                        metaSet = photo.Dispatcher.InvokeAsync(() => photo.Set(metadata));
                        img.BeginInit();
                        img.StreamSource = data;
                        if (3 * metadata.Width > 2 * metadata.Height)
                        {
                            img.DecodePixelWidth = 3 * ThumbnailHeight / 2;
                        }
                        else
                        {
                            img.DecodePixelHeight = ThumbnailHeight;
                        }
                        img.CacheOption = BitmapCacheOption.OnLoad;
                        img.Rotation    = Exif.OrienationToRotation(metadata.Orientation);
                        img.EndInit();
                        img.Freeze();
                    }
                    await metaSet;
                } catch {
                    mmap.Dispose();
                    throw;
                }
                if (!await photo.Dispatcher.InvokeAsync(() => {
                    if (photo.Disposed)
                    {
                        return(false);
                    }
                    photo.mmap = mmap;
                    photo.loader = this;
                    photo.ThumbImage = img;
                    return(true);
                }))
                {
                    mmap.Dispose();
                }
            } catch (Exception ex) {
                await photo.Dispatcher.InvokeAsync(() => {
                    MessageBox.Show(ex.ToString(),
                                    string.Format("Error loading {0}\n\n{1}",
                                                  photo.FileName, ex),
                                    MessageBoxButton.OK,
                                    MessageBoxImage.Error);
                    photo.Dispose();
                    list.Remove(photo);
                });
            }
        }
        private async static Task setImage(Photo photo, Photo.Metadata metadata, bool mustLock)
        {
            bool locked = false;

            if (photo.Disposed)
            {
                return;
            }
            try {
                if (mustLock)
                {
                    // To avoid dispose colliding with setImage.
                    await photo.loadLock.WaitAsync();

                    locked = true;
                }
                if (photo.Disposed)
                {
                    return;
                }
                var(mmap, fullImageStream) = await photo.Dispatcher.InvokeAsync(
                    () => (photo.mmap, photo.fullImageStream));

                if (mmap == null)
                {
                    // disposed
                    return;
                }
                if (fullImageStream == null)
                {
                    fullImageStream = new UnsafeMemoryMapStream(
                        mmap.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read),
                        FileAccess.Read);
                    await photo.Dispatcher.InvokeAsync(() => {
                        var p = Interlocked.CompareExchange(
                            ref photo.fullImageStream, fullImageStream, null);
                        if (p != null)
                        {
                            fullImageStream.Dispose();
                            fullImageStream = p;
                        }
                    });
                }
                var img = new BitmapImage();
                try {
                    img.BeginInit();
                    img.StreamSource = fullImageStream.Stream;
                    makeFullImage(metadata, img);
                } catch (Exception ex) {
                    await photo.Dispatcher.InvokeAsync(() => {
                        MessageBox.Show(ex.ToString(),
                                        string.Format("Error loading {0}\n\n{1}",
                                                      photo.FileName, ex),
                                        MessageBoxButton.OK,
                                        MessageBoxImage.Error);
                    });

                    return;
                }
                await photo.Dispatcher.InvokeAsync(() => {
                    photo.FullImage = img;
                });
            } finally {
                if (locked)
                {
                    photo.loadLock.Release();
                }
            }
            // Now is a good time to do a gen-0 GC to make sure the image we
            // just loaded gets promoted to at least gen1 before its caching
            // expires.
            GC.Collect(0);
        }