public static void CompressWithoutTransparency(Surface surface, EncoderOptions options, AvifProgressCallback avifProgress, ref uint progressDone, uint progressTotal, CICPColorData colorInfo, out CompressedAV1Image color) { BitmapData bitmapData = new BitmapData { scan0 = surface.Scan0.Pointer, width = (uint)surface.Width, height = (uint)surface.Height, stride = (uint)surface.Stride }; ProgressContext progressContext = new ProgressContext(avifProgress, progressDone, progressTotal); using (CompressedAV1DataAllocator allocator = new CompressedAV1DataAllocator(1)) { IntPtr colorImage; CompressedAV1OutputAlloc outputAllocDelegate = new CompressedAV1OutputAlloc(allocator.Allocate); EncoderStatus status = EncoderStatus.Ok; if (IntPtr.Size == 8) { status = AvifNative_64.CompressImage(ref bitmapData, options, progressContext, ref colorInfo, outputAllocDelegate, out colorImage, IntPtr.Zero); } else { status = AvifNative_86.CompressImage(ref bitmapData, options, progressContext, ref colorInfo, outputAllocDelegate, out colorImage, IntPtr.Zero); } GC.KeepAlive(outputAllocDelegate); if (status != EncoderStatus.Ok) { HandleError(status, allocator.ExceptionInfo); } color = new CompressedAV1Image(allocator.GetCompressedAV1Data(colorImage), surface.Width, surface.Height, options.yuvFormat); } progressDone = progressContext.progressDone; GC.KeepAlive(avifProgress); }
private AvifWriterItem(uint id, string name, CompressedAV1Image image, bool isAlphaImage) { if (image is null) { ExceptionUtil.ThrowArgumentNullException(nameof(image)); } this.Id = id; this.Name = name; this.Image = image; this.IsAlphaImage = isAlphaImage; this.ContentBytes = null; this.ItemInfoEntry = new AV01ItemInfoEntryBox(id, name); this.ItemLocation = new ItemLocationEntry(id, image.Data.ByteLength); this.ItemReferences = new List <ItemReferenceEntryBox>(); }
private ImageStateInfo InitializeFromSingleImage(CompressedAV1Image color, CompressedAV1Image alpha) { ulong mediaDataBoxContentSize = color.Data.ByteLength; uint itemId = FirstItemId; AvifWriterItem colorItem = AvifWriterItem.CreateFromImage(itemId, "Color", color, false); itemId++; this.PrimaryItemId = colorItem.Id; this.items.Add(colorItem); if (alpha != null) { AvifWriterItem alphaItem = AvifWriterItem.CreateFromImage(itemId, "Alpha", alpha, true); itemId++; alphaItem.ItemReferences.Add(new ItemReferenceEntryBox(alphaItem.Id, ReferenceTypes.AuxiliaryImage, this.PrimaryItemId)); this.AlphaItemId = alphaItem.Id; this.items.Add(alphaItem); mediaDataBoxContentSize += alpha.Data.ByteLength; } return(new ImageStateInfo(mediaDataBoxContentSize, itemId)); }
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 ImageStateInfo InitializeFromImageGrid(IReadOnlyList <CompressedAV1Image> colorImages, IReadOnlyList <CompressedAV1Image> alphaImages, ImageGridMetadata imageGridMetadata) { ulong mediaDataBoxContentSize = 0; uint itemId = FirstItemId; List <uint> colorImageIds = new List <uint>(colorImages.Count); List <uint> alphaImageIds = alphaImages != null ? new List <uint>(alphaImages.Count) : null; for (int i = 0; i < colorImages.Count; i++) { CompressedAV1Image color = colorImages[i]; AvifWriterItem colorItem = AvifWriterItem.CreateFromImage(itemId, null, color, false); itemId++; colorImageIds.Add(colorItem.Id); this.items.Add(colorItem); mediaDataBoxContentSize += color.Data.ByteLength; if (alphaImages != null) { CompressedAV1Image alpha = alphaImages[i]; AvifWriterItem alphaItem = AvifWriterItem.CreateFromImage(itemId, null, alpha, true); itemId++; alphaItem.ItemReferences.Add(new ItemReferenceEntryBox(alphaItem.Id, ReferenceTypes.AuxiliaryImage, colorItem.Id)); alphaImageIds.Add(alphaItem.Id); this.items.Add(alphaItem); mediaDataBoxContentSize += alpha.Data.ByteLength; } } ulong gridDescriptorLength; if (imageGridMetadata.OutputHeight > ushort.MaxValue || imageGridMetadata.OutputWidth > ushort.MaxValue) { gridDescriptorLength = ImageGridDescriptor.LargeDescriptorLength; } else { gridDescriptorLength = ImageGridDescriptor.SmallDescriptorLength; } AvifWriterItem colorGridItem = AvifWriterItem.CreateFromImageGrid(itemId, "Color", 0, gridDescriptorLength); itemId++; colorGridItem.ItemReferences.Add(new ItemReferenceEntryBox(colorGridItem.Id, ReferenceTypes.DerivedImage, colorImageIds)); this.PrimaryItemId = colorGridItem.Id; this.items.Add(colorGridItem); if (alphaImages != null) { // The ImageGridDescriptor is shared between the color and alpha image. AvifWriterItem alphaGridItem = AvifWriterItem.CreateFromImageGrid(itemId, "Alpha", 0, gridDescriptorLength); itemId++; alphaGridItem.ItemReferences.Add(new ItemReferenceEntryBox(alphaGridItem.Id, ReferenceTypes.AuxiliaryImage, colorGridItem.Id)); alphaGridItem.ItemReferences.Add(new ItemReferenceEntryBox(alphaGridItem.Id, ReferenceTypes.DerivedImage, alphaImageIds)); this.AlphaItemId = alphaGridItem.Id; this.items.Add(alphaGridItem); } return(new ImageStateInfo(mediaDataBoxContentSize, itemId)); }
public static AvifWriterItem CreateFromImage(uint itemId, string name, CompressedAV1Image image, bool isAlphaImage) { return(new AvifWriterItem(itemId, name, image, isAlphaImage)); }