private void ProcessColorImage(Surface fullSurface) { CICPColorData?colorConversionInfo = null; if (this.colorInfoBox is NclxColorInformation nclxColorInformation) { colorConversionInfo = new CICPColorData { colorPrimaries = nclxColorInformation.ColorPrimaries, transferCharacteristics = nclxColorInformation.TransferCharacteristics, matrixCoefficients = nclxColorInformation.MatrixCoefficients, fullRange = nclxColorInformation.FullRange }; } if (this.colorGridInfo != null) { FillColorImageGrid(colorConversionInfo, fullSurface); } else { DecodeInfo decodeInfo = new DecodeInfo { tileColumnIndex = 0, tileRowIndex = 0, expectedWidth = (uint)fullSurface.Width, expectedHeight = (uint)fullSurface.Height }; DecodeColorImage(this.primaryItemId, decodeInfo, colorConversionInfo, fullSurface); SetImageColorData(colorConversionInfo, decodeInfo); } }
public static string TrySerialize(CICPColorData cicpColor) { // The identity matrix coefficient is never serialized. if (cicpColor.matrixCoefficients == CICPMatrixCoefficients.Identity) { return(null); } if (cicpColor.colorPrimaries == CICPColorPrimaries.Unspecified || cicpColor.transferCharacteristics == CICPTransferCharacteristics.Unspecified || cicpColor.matrixCoefficients == CICPMatrixCoefficients.Unspecified) { return(null); } ushort colorPrimaries = (ushort)cicpColor.colorPrimaries; ushort transferCharacteristics = (ushort)cicpColor.transferCharacteristics; ushort matrixCoefficients = (ushort)cicpColor.matrixCoefficients; return(string.Format(CultureInfo.InvariantCulture, "<CICP {0}=\"{1}\" {2}=\"{3}\" {4}=\"{5}\"/>", ColorPrimariesPropertyName, colorPrimaries.ToString(CultureInfo.InvariantCulture), TransferCharacteristicsPropertyName, transferCharacteristics.ToString(CultureInfo.InvariantCulture), MatrixCoefficientsPropertyName, matrixCoefficients.ToString(CultureInfo.InvariantCulture))); }
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); }
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); } } }
public static void DecompressColor(AvifItemData colorImage, CICPColorData?colorConversionInfo, DecodeInfo decodeInfo, Surface fullSurface) { if (colorImage is null) { ExceptionUtil.ThrowArgumentNullException(nameof(colorImage)); } if (decodeInfo is null) { ExceptionUtil.ThrowArgumentNullException(nameof(decodeInfo)); } if (fullSurface is null) { ExceptionUtil.ThrowArgumentNullException(nameof(fullSurface)); } DecoderStatus status = DecoderStatus.Ok; unsafe { colorImage.UseBufferPointer((ptr, length) => { BitmapData bitmapData = new BitmapData { scan0 = fullSurface.Scan0.Pointer, width = (uint)fullSurface.Width, height = (uint)fullSurface.Height, stride = (uint)fullSurface.Stride }; UIntPtr colorImageSize = new UIntPtr(length); if (colorConversionInfo.HasValue) { CICPColorData colorData = colorConversionInfo.Value; if (IntPtr.Size == 8) { status = AvifNative_64.DecompressColorImage(ptr, colorImageSize, ref colorData, decodeInfo, ref bitmapData); } else { status = AvifNative_86.DecompressColorImage(ptr, colorImageSize, ref colorData, decodeInfo, ref bitmapData); } } else { if (IntPtr.Size == 8) { status = AvifNative_64.DecompressColorImage(ptr, colorImageSize, IntPtr.Zero, decodeInfo, ref bitmapData); } else { status = AvifNative_86.DecompressColorImage(ptr, colorImageSize, IntPtr.Zero, decodeInfo, ref bitmapData); } } }); } if (status != DecoderStatus.Ok) { HandleError(status); } }