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); }
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; }
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 int GetItemCount(IReadOnlyList <CompressedAV1Image> colorImages, IReadOnlyList <CompressedAV1Image> alphaImages, AvifMetadata metadata) { int count; if (colorImages.Count == 1) { count = 1; } else { // Add one item for the grid image. count = 1 + colorImages.Count; } if (alphaImages != null) { // The color and alpha lists will always have the same number of images. count *= 2; } byte[] exif = metadata.GetExifBytesReadOnly(); if (exif != null && exif.Length > 0) { count++; } byte[] xmp = metadata.GetXmpBytesReadOnly(); if (xmp != null && xmp.Length > 0) { count++; } return(count); }