public ManagedAvifItemData(int length, IByteArrayPool pool) : base() { this.buffer = pool.Rent(length); this.Length = (ulong)length; this.arrayPool = pool; }
public static Document Load(Stream input, IByteArrayPool arrayPool) { Document doc = null; using (AvifReader reader = new AvifReader(input, leaveOpen: true, arrayPool)) { Surface surface = null; bool disposeSurface = true; try { surface = reader.Decode(); doc = new Document(surface.Width, surface.Height); AddAvifMetadataToDocument(doc, reader, arrayPool); doc.Layers.Add(Layer.CreateBackgroundLayer(surface, takeOwnership: true)); disposeSurface = false; } finally { if (disposeSurface) { surface?.Dispose(); } } } return(doc); }
public AvifWriter(IReadOnlyList <CompressedAV1Image> colorImages, IReadOnlyList <CompressedAV1Image> alphaImages, AvifMetadata metadata, ImageGridMetadata imageGridMetadata, YUVChromaSubsampling chromaSubsampling, ColorInformationBox colorInformationBox, ProgressEventHandler progressEventHandler, uint progressDone, uint progressTotal, IByteArrayPool arrayPool) { this.state = new AvifWriterState(colorImages, alphaImages, imageGridMetadata, metadata, arrayPool); this.arrayPool = arrayPool; this.colorImageIsGrayscale = chromaSubsampling == YUVChromaSubsampling.Subsampling400; this.colorInformationBox = colorInformationBox; this.progressCallback = progressEventHandler; this.progressDone = progressDone; this.progressTotal = progressTotal; this.fileTypeBox = new FileTypeBox(chromaSubsampling); this.metaBox = new MetaBox(this.state.PrimaryItemId, this.state.Items.Count, this.state.MediaDataBoxContentSize > uint.MaxValue, this.state.ItemDataBox); PopulateMetaBox(); }
public AvifWriterState(IReadOnlyList <CompressedAV1Image> colorImages, IReadOnlyList <CompressedAV1Image> alphaImages, ImageGridMetadata imageGridMetadata, AvifMetadata metadata, IByteArrayPool arrayPool) { if (colorImages is null) { ExceptionUtil.ThrowArgumentNullException(nameof(colorImages)); } if (metadata is null) { ExceptionUtil.ThrowArgumentNullException(nameof(metadata)); } if (arrayPool is null) { ExceptionUtil.ThrowArgumentNullException(nameof(arrayPool)); } this.ImageGrid = imageGridMetadata; this.items = new List <AvifWriterItem>(GetItemCount(colorImages, alphaImages, metadata)); Initialize(colorImages, alphaImages, imageGridMetadata, metadata, arrayPool); }
/// <summary> /// Initializes a new instance of the <see cref="AvifReader"/> class. /// </summary> /// <param name="parser">The parser.</param> /// <exception cref="ArgumentNullException"><paramref name="input"/> is null.</exception> public AvifReader(Stream input, bool leaveOpen, IByteArrayPool arrayPool) { if (input is null) { ExceptionUtil.ThrowArgumentNullException(nameof(input)); } // The parser is initialized first because it will throw an exception // if the AVIF file is invalid or not supported. this.parser = new AvifParser(input, leaveOpen, arrayPool); this.primaryItemId = this.parser.GetPrimaryItemId(); this.alphaItemId = this.parser.GetAlphaItemId(this.primaryItemId); this.colorInfoBox = this.parser.TryGetColorInfoBox(this.primaryItemId); this.parser.GetTransformationProperties(this.primaryItemId, out this.cleanApertureBox, out this.imageRotateBox, out this.imageMirrorBox); this.colorGridInfo = this.parser.TryGetImageGridInfo(this.primaryItemId); if (this.alphaItemId != 0) { this.alphaGridInfo = this.parser.TryGetImageGridInfo(this.alphaItemId); } else { this.alphaGridInfo = null; } }
public ReadonlyStreamViewForSectorList(SectorList sectorChain, long length, Stream sourceStream, IByteArrayPool byteArrayPool) { _sectorChain = sectorChain; _sourceStream = sourceStream; _byteArrayPool = byteArrayPool; Length = length; }
/// <summary> /// Instantiates the <see cref="NetworkFetchProducer"/>. /// </summary> public NetworkFetchProducer( IPooledByteBufferFactory pooledByteBufferFactory, IByteArrayPool byteArrayPool, INetworkFetcher <FetchState> networkFetcher) { _pooledByteBufferFactory = pooledByteBufferFactory; _byteArrayPool = byteArrayPool; _networkFetcher = networkFetcher; }
/// <summary> /// Instantiates the <see cref="ProgressiveJpegParser"/>. /// </summary> /// <param name="byteArrayPool"></param> public ProgressiveJpegParser(IByteArrayPool byteArrayPool) { _byteArrayPool = Preconditions.CheckNotNull(byteArrayPool); _bytesParsed = 0; _lastByteRead = 0; _nextFullScanNumber = 0; _bestScanNumber = 0; _parserState = READ_FIRST_JPEG_BYTE; _bestScanEndOffsetList = new ConcurrentDictionary <int, int>(); }
public BigEndianBinaryWriter(Stream stream, bool leaveOpen, IByteArrayPool arrayPool) { if (stream is null) { ExceptionUtil.ThrowArgumentNullException(nameof(stream)); } this.stream = stream; this.leaveOpen = leaveOpen; this.buffer = new byte[sizeof(ulong)]; this.arrayPool = arrayPool; }
public AvifParser(Stream stream, bool leaveOpen, IByteArrayPool arrayPool) { if (stream is null) { ExceptionUtil.ThrowArgumentNullException(nameof(stream)); } this.arrayPool = arrayPool; this.reader = new EndianBinaryReader(stream, Endianess.Big, leaveOpen, arrayPool); Parse(); this.fileLength = (ulong)stream.Length; }
public void CopyTo(Stream destinationStream, IByteArrayPool byteArrayPool, int position, long count) { if (data != null) { destinationStream.Write(data, 0, data.Length); } else { if (IsStreamed) { var offset = size + id * size + position; stream.CopyTo(destinationStream, byteArrayPool, offset, Math.Min(size, count)); } } }
/// <summary> /// Instantiates the <see cref="DecodeProducer"/>. /// </summary> public DecodeProducer( IByteArrayPool byteArrayPool, IExecutorService executor, ImageDecoder imageDecoder, IProgressiveJpegConfig progressiveJpegConfig, bool downsampleEnabled, bool downsampleEnabledForNetwork, IProducer <EncodedImage> inputProducer) { _byteArrayPool = Preconditions.CheckNotNull(byteArrayPool); _executor = Preconditions.CheckNotNull(executor); _imageDecoder = Preconditions.CheckNotNull(imageDecoder); _progressiveJpegConfig = Preconditions.CheckNotNull(progressiveJpegConfig); _downsampleEnabled = downsampleEnabled; _downsampleEnabledForNetwork = downsampleEnabledForNetwork; _inputProducer = Preconditions.CheckNotNull(inputProducer); }
/// <summary> /// Parses the EXIF data into a collection of properties. /// </summary> /// <param name="exifBytes">The EXIF bytes.</param> /// <returns> /// A collection containing the EXIF properties. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="exifBytes"/> is null. /// /// -or- /// /// <paramref name="arrayPool"/> is null. /// </exception> internal static ExifValueCollection Parse(byte[] exifBytes, IByteArrayPool arrayPool) { if (exifBytes is null) { throw new ArgumentNullException(nameof(exifBytes)); } List <MetadataEntry> metadataEntries = new List <MetadataEntry>(); MemoryStream stream = null; try { stream = new MemoryStream(exifBytes); Endianess?byteOrder = TryDetectTiffByteOrder(stream); if (byteOrder.HasValue) { using (EndianBinaryReader reader = new EndianBinaryReader(stream, byteOrder.Value, arrayPool)) { stream = null; ushort signature = reader.ReadUInt16(); if (signature == TiffConstants.Signature) { uint ifdOffset = reader.ReadUInt32(); List <ParserIFDEntry> entries = ParseDirectories(reader, ifdOffset); metadataEntries.AddRange(ConvertIFDEntriesToMetadataEntries(reader, entries)); } } } } catch (EndOfStreamException) { } finally { stream?.Dispose(); } return(new ExifValueCollection(metadataEntries)); }
private void Initialize(IReadOnlyList <CompressedAV1Image> colorImages, IReadOnlyList <CompressedAV1Image> alphaImages, ImageGridMetadata imageGridMetadata, AvifMetadata metadata, IByteArrayPool arrayPool) { ImageStateInfo result; if (imageGridMetadata != null) { result = InitializeFromImageGrid(colorImages, alphaImages, imageGridMetadata); this.ItemDataBox = CreateItemDataBox(imageGridMetadata, arrayPool); } else { result = InitializeFromSingleImage(colorImages[0], alphaImages?[0]); this.ItemDataBox = null; } uint itemId = result.NextId; ulong mediaDataBoxContentSize = result.MediaDataBoxContentSize; byte[] exif = metadata.GetExifBytesReadOnly(); if (exif != null && exif.Length > 0) { AvifWriterItem exifItem = AvifWriterItem.CreateFromExif(itemId, exif); itemId++; exifItem.ItemReferences.Add(new ItemReferenceEntryBox(exifItem.Id, ReferenceTypes.ContentDescription, this.PrimaryItemId)); this.items.Add(exifItem); mediaDataBoxContentSize += (ulong)exifItem.ContentBytes.Length; } byte[] xmp = metadata.GetXmpBytesReadOnly(); if (xmp != null && xmp.Length > 0) { AvifWriterItem xmpItem = AvifWriterItem.CreateFromXmp(itemId, xmp); xmpItem.ItemReferences.Add(new ItemReferenceEntryBox(xmpItem.Id, ReferenceTypes.ContentDescription, this.PrimaryItemId)); this.items.Add(xmpItem); mediaDataBoxContentSize += (ulong)xmpItem.ContentBytes.Length; } this.MediaDataBoxContentSize = mediaDataBoxContentSize; }
/// <summary> /// Initializes a new instance of the <see cref="EndianBinaryReader"/> class. /// </summary> /// <param name="stream">The stream.</param> /// <param name="byteOrder">The byte order of the stream.</param> /// <param name="leaveOpen"> /// <see langword="true"/> to leave the stream open after the EndianBinaryReader is disposed; otherwise, <see langword="false"/> /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="stream"/> is null. /// /// -or- /// /// <paramref name="arrayPool"/> is null. /// </exception> public EndianBinaryReader(Stream stream, Endianess byteOrder, bool leaveOpen, IByteArrayPool arrayPool) { if (arrayPool is null) { ExceptionUtil.ThrowArgumentNullException(nameof(arrayPool)); } this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); this.bufferSize = (int)Math.Min(stream.Length, MaxBufferSize); this.buffer = arrayPool.Rent(this.bufferSize); this.endianess = byteOrder; this.leaveOpen = leaveOpen; this.arrayPool = arrayPool; this.readOffset = 0; this.readLength = 0; this.disposed = false; }
/// <summary> /// Instantiates the <see cref="ProducerFactory"/> /// </summary> /// <param name="byteArrayPool"> /// The IByteArrayPool used by DecodeProducer. /// </param> /// <param name="imageDecoder"> /// The image decoder. /// </param> /// <param name="progressiveJpegConfig"> /// The progressive Jpeg configuration. /// </param> /// <param name="downsampleEnabled"> /// Enabling downsample. /// </param> /// <param name="resizeAndRotateEnabledForNetwork"> /// Enabling resize and rotate. /// </param> /// <param name="executorSupplier"> /// The supplier for tasks. /// </param> /// <param name="pooledByteBufferFactory"> /// The factory that allocates IPooledByteBuffer memory. /// </param> /// <param name="bitmapMemoryCache"> /// The memory cache for CloseableImage. /// </param> /// <param name="encodedMemoryCache"> /// The memory cache for IPooledByteBuffer. /// </param> /// <param name="defaultBufferedDiskCache"> /// The default buffered disk cache. /// </param> /// <param name="smallImageBufferedDiskCache"> /// The buffered disk cache used for small images. /// </param> /// <param name="cacheKeyFactory"> /// The factory that creates cache keys for the pipeline. /// </param> /// <param name="platformBitmapFactory"> /// The bitmap factory used for post process. /// </param> /// <param name="flexByteArrayPool"> /// The memory pool used for post process. /// </param> /// <param name="forceSmallCacheThresholdBytes"> /// The threshold set for using the small buffered disk cache. /// </param> public ProducerFactory( IByteArrayPool byteArrayPool, ImageDecoder imageDecoder, IProgressiveJpegConfig progressiveJpegConfig, bool downsampleEnabled, bool resizeAndRotateEnabledForNetwork, IExecutorSupplier executorSupplier, IPooledByteBufferFactory pooledByteBufferFactory, IMemoryCache <ICacheKey, CloseableImage> bitmapMemoryCache, IMemoryCache <ICacheKey, IPooledByteBuffer> encodedMemoryCache, BufferedDiskCache defaultBufferedDiskCache, BufferedDiskCache smallImageBufferedDiskCache, ICacheKeyFactory cacheKeyFactory, PlatformBitmapFactory platformBitmapFactory, FlexByteArrayPool flexByteArrayPool, int forceSmallCacheThresholdBytes) { _forceSmallCacheThresholdBytes = forceSmallCacheThresholdBytes; _byteArrayPool = byteArrayPool; _imageDecoder = imageDecoder; _progressiveJpegConfig = progressiveJpegConfig; _downsampleEnabled = downsampleEnabled; _resizeAndRotateEnabledForNetwork = resizeAndRotateEnabledForNetwork; _executorSupplier = executorSupplier; _pooledByteBufferFactory = pooledByteBufferFactory; _bitmapMemoryCache = bitmapMemoryCache; _encodedMemoryCache = encodedMemoryCache; _defaultBufferedDiskCache = defaultBufferedDiskCache; _smallImageBufferedDiskCache = smallImageBufferedDiskCache; _cacheKeyFactory = cacheKeyFactory; _platformBitmapFactory = platformBitmapFactory; _flexByteArrayPool = flexByteArrayPool; }
/// <summary> /// Instantiates the <see cref="PooledByteStreams"/>. /// </summary> public PooledByteStreams(IByteArrayPool byteArrayPool) : this(byteArrayPool, DEFAULT_TEMP_BUF_SIZE) { }
public static void CopyTo(this Stream sourceStream, Stream destinationStream, IByteArrayPool byteArrayPool, long position, long size) { sourceStream.Seek(position, SeekOrigin.Begin); const int defaultBufferLength = 4096; var bufferLength = (int)Math.Min(defaultBufferLength, size); var buffer = byteArrayPool.Rent(bufferLength); var readCount = 0; while (readCount < size) { var count = (int)Math.Min(size - readCount, bufferLength); var n = sourceStream.Read(buffer, 0, count); if (n == 0) { break; } readCount += n; destinationStream.Write(buffer, 0, n); } byteArrayPool.Return(buffer); }
public static void Save(Document document, Stream output, int quality, CompressionSpeed compressionSpeed, YUVChromaSubsampling chromaSubsampling, bool preserveExistingTileSize, int?maxEncoderThreadsOverride, Surface scratchSurface, ProgressEventHandler progressCallback, IByteArrayPool arrayPool) { using (RenderArgs args = new RenderArgs(scratchSurface)) { document.Render(args, true); } bool grayscale = IsGrayscaleImage(scratchSurface); AvifMetadata metadata = CreateAvifMetadata(document); EncoderOptions options = new EncoderOptions { quality = quality, compressionSpeed = compressionSpeed, // YUV 4:0:0 is always used for gray-scale images because it // produces the smallest file size with no quality loss. yuvFormat = grayscale ? YUVChromaSubsampling.Subsampling400 : chromaSubsampling, maxThreads = maxEncoderThreadsOverride ?? Environment.ProcessorCount }; // Use BT.709 with sRGB transfer characteristics as the default. CICPColorData colorConversionInfo = new CICPColorData { colorPrimaries = CICPColorPrimaries.BT709, transferCharacteristics = CICPTransferCharacteristics.Srgb, matrixCoefficients = CICPMatrixCoefficients.BT709, fullRange = true }; if (quality == 100 && !grayscale) { // The Identity matrix coefficient places the RGB values into the YUV planes without any conversion. // This reduces the compression efficiency, but allows for fully lossless encoding. options.yuvFormat = YUVChromaSubsampling.IdentityMatrix; // These CICP color values are from the AV1 Bitstream & Decoding Process Specification. colorConversionInfo = new CICPColorData { colorPrimaries = CICPColorPrimaries.BT709, transferCharacteristics = CICPTransferCharacteristics.Srgb, matrixCoefficients = CICPMatrixCoefficients.Identity, fullRange = true }; } else { Metadata docMetadata = document.Metadata; // Look for NCLX meta-data if the CICP meta-data was not found. // This preserves backwards compatibility with PDN files created by // previous versions of this plugin. string serializedData = docMetadata.GetUserValue(CICPMetadataName) ?? docMetadata.GetUserValue(NclxMetadataName); if (serializedData != null) { CICPColorData?colorData = CICPSerializer.TryDeserialize(serializedData); if (colorData.HasValue) { colorConversionInfo = colorData.Value; } } } ImageGridMetadata imageGridMetadata = TryGetImageGridMetadata(document, options.compressionSpeed, options.yuvFormat, preserveExistingTileSize); bool hasTransparency = HasTransparency(scratchSurface); CompressedAV1ImageCollection colorImages = new CompressedAV1ImageCollection(imageGridMetadata?.TileCount ?? 1); CompressedAV1ImageCollection alphaImages = hasTransparency ? new CompressedAV1ImageCollection(colorImages.Capacity) : null; // Progress is reported at the following stages: // 1. Before converting the image to the YUV color space // 2. Before compressing the color image // 3. After compressing the color image // 4. After compressing the alpha image (if present) // 5. After writing the color image to the file // 6. After writing the alpha image to the file (if present) uint progressDone = 0; uint progressTotal = hasTransparency ? 6U : 4U; if (colorImages.Capacity > 1) { progressTotal *= (uint)colorImages.Capacity; } try { Rectangle[] windowRectangles = GetTileWindowRectangles(imageGridMetadata, document); for (int i = 0; i < colorImages.Capacity; i++) { CompressedAV1Image color = null; CompressedAV1Image alpha = null; try { Rectangle windowRect = windowRectangles[i]; using (Surface window = scratchSurface.CreateWindow(windowRect)) { if (hasTransparency) { AvifNative.CompressWithTransparency(window, options, ReportCompressionProgress, ref progressDone, progressTotal, colorConversionInfo, out color, out alpha); } else { AvifNative.CompressWithoutTransparency(window, options, ReportCompressionProgress, ref progressDone, progressTotal, colorConversionInfo, out color); } } colorImages.Add(color); color = null; if (hasTransparency) { alphaImages.Add(alpha); alpha = null; } } finally { color?.Dispose(); alpha?.Dispose(); } } ColorInformationBox colorInformationBox; byte[] iccProfileBytes = metadata.GetICCProfileBytesReadOnly(); if (iccProfileBytes != null && iccProfileBytes.Length > 0) { colorInformationBox = new IccProfileColorInformation(iccProfileBytes); } else { colorInformationBox = new NclxColorInformation(colorConversionInfo.colorPrimaries, colorConversionInfo.transferCharacteristics, colorConversionInfo.matrixCoefficients, colorConversionInfo.fullRange); } AvifWriter writer = new AvifWriter(colorImages, alphaImages, metadata, imageGridMetadata, options.yuvFormat, colorInformationBox, progressCallback, progressDone, progressTotal, arrayPool); writer.WriteTo(output); } finally { colorImages?.Dispose(); alphaImages?.Dispose(); } bool ReportCompressionProgress(uint done, uint total) { try { progressCallback?.Invoke(null, new ProgressEventArgs(((double)done / total) * 100.0, true)); return(true); } catch (OperationCanceledException) { return(false); } } }
private static void AddAvifMetadataToDocument(Document doc, AvifReader reader, IByteArrayPool arrayPool) { byte[] exifBytes = reader.GetExifData(); if (exifBytes != null) { ExifValueCollection exifValues = ExifParser.Parse(exifBytes, arrayPool); if (exifValues != null) { exifValues.Remove(MetadataKeys.Image.InterColorProfile); // The HEIF specification states that the EXIF orientation tag is only // informational and should not be used to rotate the image. // See https://github.com/strukturag/libheif/issues/227#issuecomment-642165942 exifValues.Remove(MetadataKeys.Image.Orientation); foreach (MetadataEntry entry in exifValues) { doc.Metadata.AddExifPropertyItem(entry.CreateExifPropertyItem()); } } } CICPColorData?imageColorData = reader.ImageColorData; if (imageColorData.HasValue) { string serializedValue = CICPSerializer.TrySerialize(imageColorData.Value); if (serializedValue != null) { doc.Metadata.SetUserValue(CICPMetadataName, serializedValue); } } ImageGridMetadata imageGridMetadata = reader.ImageGridMetadata; if (imageGridMetadata != null) { string serializedValue = imageGridMetadata.SerializeToString(); if (serializedValue != null) { doc.Metadata.SetUserValue(ImageGridName, serializedValue); } } byte[] iccProfileBytes = reader.GetICCProfile(); if (iccProfileBytes != null) { doc.Metadata.AddExifPropertyItem(ExifSection.Image, unchecked ((ushort)ExifTagID.IccProfileData), new ExifValue(ExifValueType.Undefined, iccProfileBytes)); } byte[] xmpBytes = reader.GetXmpData(); if (xmpBytes != null) { XmpPacket xmpPacket = XmpPacket.TryParse(xmpBytes); if (xmpPacket != null) { doc.Metadata.SetXmpPacket(xmpPacket); } } }
#pragma warning restore IDE0032 // Use auto property /// <summary> /// Initializes a new instance of the <see cref="EndianBinaryReader"/> class. /// </summary> /// <param name="stream">The stream.</param> /// <param name="byteOrder">The byte order of the stream.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="stream"/> is null. /// /// -or- /// /// <paramref name="arrayPool"/> is null. /// </exception> public EndianBinaryReader(Stream stream, Endianess byteOrder, IByteArrayPool arrayPool) : this(stream, byteOrder, false, arrayPool) { }
/// <summary> /// Instantiates the <see cref="PooledByteStreams"/>. /// </summary> public PooledByteStreams(IByteArrayPool byteArrayPool, int tempBufSize) { Preconditions.CheckArgument(tempBufSize > 0); _tempBufSize = tempBufSize; _byteArrayPool = byteArrayPool; }
private static ItemDataBox CreateItemDataBox(ImageGridMetadata imageGridMetadata, IByteArrayPool arrayPool) { ImageGridDescriptor imageGridDescriptor = new ImageGridDescriptor(imageGridMetadata); byte[] dataBoxBuffer = new byte[imageGridDescriptor.GetSize()]; MemoryStream stream = null; try { stream = new MemoryStream(dataBoxBuffer); using (BigEndianBinaryWriter writer = new BigEndianBinaryWriter(stream, leaveOpen: false, arrayPool)) { stream = null; // The ImageGridDescriptor is shared between the color and alpha image. imageGridDescriptor.Write(writer); } } finally { stream?.Dispose(); } return(new ItemDataBox(dataBoxBuffer)); }
public void Initialize() { _testExecutor = Executors.NewFixedThreadPool(MAX_DEGREE_OF_PARALLELISM); _poolFactory = new PoolFactory(PoolConfig.NewBuilder().Build()); _pooledByteBufferFactory = _poolFactory.PooledByteBufferFactory; _byteArrayPool = _poolFactory.SmallByteArrayPool; _producerListener = new ProducerListenerImpl( (_, __) => { }, (requestId, producerName, eventName) => { if (eventName.Equals(NetworkFetchProducer.INTERMEDIATE_RESULT_PRODUCER_EVENT)) { ++_intermediateResultProducerEventCalls; } }, (_, __, ___) => { ++_onProducerFinishWithSuccessFuncCalls; }, (requestId, producerName, error, extraMap) => { _internalRequestId = requestId; _internalProducerName = producerName; _internalError = error; _internalExtraMap = extraMap; ++_onProducerFinishWithFailureFuncCalls; }, (_, __, ___) => { ++_onProducerFinishWithCancellationFuncCalls; }, (_) => { return(false); }); _consumer = new BaseConsumerImpl <EncodedImage>( (_, isLast) => { ++_onNewResultImplCalls; if (isLast) { _completion.Set(); } }, (_) => { _completion.Set(); }, () => { _completion.Set(); }, (_) => { }); _networkFetchProducer = new NetworkFetchProducer( _pooledByteBufferFactory, _byteArrayPool, _networkFetcher); _completion = new ManualResetEvent(false); }