forked from cyanfish/naps2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProcessedImage.cs
195 lines (166 loc) · 6.85 KB
/
ProcessedImage.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
using NAPS2.Pdf;
namespace NAPS2.Images;
/// <summary>
/// An image that has gone through the scanning (or importing) process. It has metadata about the image, possibly
/// additional post-processing data from the scan, and may have transformations that have been applied during or after
/// the scan.
///
/// This type is immutable and uses a reference counting model for the underlying image storage. You can create a new
/// reference with Clone() that will need to be disposed, and the underlying image storage will only be disposed once
/// all related instances are disposed (or the parent ScanningContext is disposed).
/// </summary>
public class ProcessedImage : IRenderableImage, IPdfRendererProvider, IDisposable, IEquatable<ProcessedImage>
{
private readonly RefCount.Token _token;
private bool _disposed;
private ProcessedImage(ImageContext imageContext, IImageStorage storage, ImageMetadata metadata,
PostProcessingData postProcessingData, TransformState transformState, RefCount refCount)
{
ImageContext = imageContext;
Storage = storage;
Metadata = metadata;
PostProcessingData = postProcessingData;
TransformState = transformState;
_token = refCount.NewToken();
}
internal ProcessedImage(ImageContext imageContext, IImageStorage storage, ImageMetadata metadata,
PostProcessingData postProcessingData, TransformState transformState, IProcessedImageOwner? owner = null)
{
ImageContext = imageContext;
Storage = storage;
Metadata = metadata;
PostProcessingData = postProcessingData;
TransformState = transformState;
var internalDisposer = new InternalDisposer(this, owner);
var refCount = new RefCount(internalDisposer);
_token = refCount.NewToken();
}
public ImageContext ImageContext { get; }
// TODO: Consider having two copies of the image on disk - one before transforms, one after.
public IImageStorage Storage { get; }
public ImageMetadata Metadata { get; }
public PostProcessingData PostProcessingData { get; }
public TransformState TransformState { get; }
/// <summary>
/// Creates a new ProcessedImage instance with the same underlying image storage/metadata and a new transform
/// appended to the TransformState. All instances will need to be disposed before the underlying image storage is
/// disposed.
/// </summary>
public ProcessedImage WithTransform(Transform transform, bool disposeSelf = false) =>
WithTransformState(TransformState.AddOrSimplify(transform), disposeSelf);
/// <summary>
/// Creates a new ProcessedImage instance with the same underlying image storage/metadata and no transforms. All
/// instances will need to be disposed before the underlying image storage is disposed.
/// </summary>
public ProcessedImage WithNoTransforms(bool disposeSelf = false) =>
WithTransformState(TransformState.Empty, disposeSelf);
/// <summary>
/// Creates a new ProcessedImage instance with the same underlying image storage/metadata and a new transform
/// state. All instances will need to be disposed before the underlying image storage is disposed.
/// </summary>
public ProcessedImage WithTransformState(TransformState newTransformState, bool disposeSelf = false)
{
var result =
new ProcessedImage(ImageContext, Storage, Metadata, PostProcessingData, newTransformState, _token.RefCount);
if (disposeSelf)
{
Dispose();
}
return result;
}
public ProcessedImage WithPostProcessingData(PostProcessingData postProcessingData, bool disposeSelf)
{
var result =
new ProcessedImage(ImageContext, Storage, Metadata, postProcessingData, TransformState, _token.RefCount);
if (disposeSelf)
{
Dispose();
}
return result;
}
/// <summary>
/// Creates a new ProcessedImage instance with the same underlying image storage/metadata. All instances will need
/// to be disposed before the underlying image storage is disposed.
/// </summary>
/// <returns></returns>
public ProcessedImage Clone()
{
lock (this)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(ProcessedImage));
}
return new(ImageContext, Storage, Metadata, PostProcessingData, TransformState, _token.RefCount);
}
}
/// <summary>
/// Creates a WeakReference wrapper for the current instance that doesn't have any effect on the instance's
/// lifetime.
/// </summary>
/// <returns></returns>
public WeakReference GetWeakReference() => new(this);
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((ProcessedImage) obj);
}
public bool Equals(ProcessedImage? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
// TODO: Should we also compare metadata?
return Storage.Equals(other.Storage) && TransformState.Equals(other.TransformState);
}
public override int GetHashCode()
{
unchecked
{
return (Storage.GetHashCode() * 397) ^ TransformState.GetHashCode();
}
}
public void Dispose()
{
lock (this)
{
_token.Dispose();
_disposed = true;
}
}
private class InternalDisposer : IDisposable
{
private readonly ProcessedImage _processedImage;
private bool _disposed;
public InternalDisposer(ProcessedImage processedImage, IProcessedImageOwner? owner)
{
_processedImage = processedImage;
owner?.Register(this);
}
public void Dispose()
{
lock (this)
{
if (_disposed)
{
return;
}
_disposed = true;
}
_processedImage.Storage.Dispose();
_processedImage.PostProcessingData.Thumbnail?.Dispose();
_processedImage.PostProcessingData.OcrCts?.Cancel();
}
}
/// <summary>
/// A class functionally equivalent to a ProcessedImage reference, but that makes explicit the intention not to
/// have ownership over or prevent disposal of the image.
/// </summary>
/// <param name="ProcessedImage">
/// The reference. Users should prefer not to directly access this unless it is understood that it may be disposed
/// at any moment.
/// </param>
public record WeakReference(ProcessedImage ProcessedImage);
IPdfRenderer IPdfRendererProvider.PdfRenderer => new PdfiumPdfRenderer();
}