public static void AddPlanarCache(PipelineContext ctx) { using var transform = default(ComPtr <IWICPlanarBitmapSourceTransform>); if (ctx.Source is not WicPixelSource wsrc || FAILED(wsrc.WicSource->QueryInterface(__uuidof <IWICPlanarBitmapSourceTransform>(), (void **)transform.GetAddressOf()))) { throw new NotSupportedException("Transform chain doesn't support planar mode. Only JPEG Decoder, Rotator, Scaler, and PixelFormatConverter are allowed"); } int ratio = ctx.Settings.HybridScaleRatio.Clamp(1, 8); uint ow = (uint)ctx.Source.Width, oh = (uint)ctx.Source.Height; uint cw = (uint)MathUtil.DivCeiling((int)ow, ratio), ch = (uint)MathUtil.DivCeiling((int)oh, ratio); var desc = stackalloc WICBitmapPlaneDescription[PlanarPixelFormats.Length]; fixed(Guid *pfmt = PlanarPixelFormats) { int bval; HRESULT.Check(transform.Get()->DoesSupportTransform(&cw, &ch, WICBitmapTransformOptions.WICBitmapTransformRotate0, WICPlanarOptions.WICPlanarOptionsDefault, pfmt, desc, (uint)PlanarPixelFormats.Length, &bval)); if (bval == 0) { throw new NotSupportedException("Requested planar transform not supported"); } } var crop = PixelArea.FromGdiRect(ctx.Settings.Crop).DeOrient(ctx.Orientation, (int)ow, (int)oh).ProportionalScale((int)ow, (int)oh, (int)cw, (int)ch); var cache = ctx.AddDispose(new WicPlanarCache(transform.Detach(), new Span <WICBitmapPlaneDescription>(desc, PlanarPixelFormats.Length), WICBitmapTransformOptions.WICBitmapTransformRotate0, cw, ch, crop)); ctx.PlanarContext = new PipelineContext.PlanarPipelineContext(cache.SourceY, cache.SourceCb, cache.SourceCr); ctx.Source = ctx.PlanarContext.SourceY; ctx.Settings.Crop = ctx.Source.Area.ReOrient(ctx.Orientation, ctx.Source.Width, ctx.Source.Height).ToGdiRect(); ctx.Settings.HybridMode = HybridScaleMode.Off; }
public static void AddPlanarCache(PipelineContext ctx) { if (!(ctx.Source.WicSource is IWICPlanarBitmapSourceTransform trans)) { throw new NotSupportedException("Transform chain doesn't support planar mode. Only JPEG Decoder, Rotator, Scaler, and PixelFormatConverter are allowed"); } int ratio = ctx.Settings.HybridScaleRatio.Clamp(1, 8); uint ow = (uint)ctx.Source.Width, oh = (uint)ctx.Source.Height; uint cw = (uint)MathUtil.DivCeiling((int)ow, ratio), ch = (uint)MathUtil.DivCeiling((int)oh, ratio); var desc = ArrayPool <WICBitmapPlaneDescription> .Shared.Rent(PlanarPixelFormats.Length); if (!trans.DoesSupportTransform(ref cw, ref ch, WICBitmapTransformOptions.WICBitmapTransformRotate0, WICPlanarOptions.WICPlanarOptionsDefault, PlanarPixelFormats, desc, (uint)PlanarPixelFormats.Length)) { throw new NotSupportedException("Requested planar transform not supported"); } var crop = PixelArea.FromGdiRect(ctx.Settings.Crop).DeOrient(ctx.Orientation, (int)ow, (int)oh).ProportionalScale((int)ow, (int)oh, (int)cw, (int)ch); var cache = ctx.AddDispose(new WicPlanarCache(trans, desc, WICBitmapTransformOptions.WICBitmapTransformRotate0, cw, ch, crop)); ArrayPool <WICBitmapPlaneDescription> .Shared.Return(desc); ctx.PlanarContext = new PipelineContext.PlanarPipelineContext(cache.SourceY, cache.SourceCb, cache.SourceCr); ctx.Source = ctx.PlanarContext.SourceY; ctx.Settings.Crop = ctx.Source.Area.ReOrient(ctx.Orientation, ctx.Source.Width, ctx.Source.Height).ToGdiRect(); ctx.Settings.HybridMode = HybridScaleMode.Off; }
public static void AddNativeScaler(PipelineContext ctx) { int ratio = ctx.Settings.HybridScaleRatio; if (ratio == 1 || ctx.ImageFrame is not WicImageFrame wicFrame || !wicFrame.SupportsNativeScale || ctx.Source is not WicPixelSource wsrc) { return; } using var transform = default(ComPtr <IWICBitmapSourceTransform>); if (FAILED(wsrc.WicSource->QueryInterface(__uuidof <IWICBitmapSourceTransform>(), (void **)transform.GetAddressOf()))) { return; } uint ow = (uint)ctx.Source.Width, oh = (uint)ctx.Source.Height; uint cw = (uint)MathUtil.DivCeiling((int)ow, ratio), ch = (uint)MathUtil.DivCeiling((int)oh, ratio); HRESULT.Check(transform.Get()->GetClosestSize(&cw, &ch)); if (cw == ow && ch == oh) { return; } var orient = ctx.Orientation; using var scaler = default(ComPtr <IWICBitmapScaler>); HRESULT.Check(Wic.Factory->CreateBitmapScaler(scaler.GetAddressOf())); HRESULT.Check(scaler.Get()->Initialize(ctx.Source.AsIWICBitmapSource(ctx), cw, ch, WICBitmapInterpolationMode.WICBitmapInterpolationModeFant)); ctx.Source = ctx.AddDispose(new ComPtr <IWICBitmapSource>((IWICBitmapSource *)scaler.Get()).AsPixelSource(ctx, nameof(IWICBitmapSourceTransform))); ctx.Settings.Crop = PixelArea.FromGdiRect(ctx.Settings.Crop).DeOrient(orient, (int)ow, (int)oh).ProportionalScale((int)ow, (int)oh, (int)cw, (int)ch).ReOrient(orient, (int)cw, (int)ch).ToGdiRect(); ctx.Settings.HybridMode = HybridScaleMode.Off; }
public static void AddNativeScaler(PipelineContext ctx) { int ratio = ctx.Settings.HybridScaleRatio; if (ratio == 1 || !(ctx.ImageFrame is WicImageFrame wicFrame) || !wicFrame.SupportsNativeScale || !(ctx.Source.WicSource is IWICBitmapSourceTransform trans)) { return; } uint ow = (uint)ctx.Source.Width, oh = (uint)ctx.Source.Height; uint cw = (uint)MathUtil.DivCeiling((int)ow, ratio), ch = (uint)MathUtil.DivCeiling((int)oh, ratio); trans.GetClosestSize(ref cw, ref ch); if (cw == ow && ch == oh) { return; } var orient = ctx.Orientation; var scaler = ctx.WicContext.AddRef(Wic.Factory.CreateBitmapScaler()); scaler.Initialize(ctx.Source.WicSource, cw, ch, WICBitmapInterpolationMode.WICBitmapInterpolationModeFant); ctx.Source = scaler.AsPixelSource(nameof(IWICBitmapSourceTransform)); ctx.Settings.Crop = PixelArea.FromGdiRect(ctx.Settings.Crop).DeOrient(orient, (int)ow, (int)oh).ProportionalScale((int)ow, (int)oh, (int)cw, (int)ch).ReOrient(orient, (int)cw, (int)ch).ToGdiRect(); ctx.Settings.HybridMode = HybridScaleMode.Off; }
public void WriteSource(PipelineContext ctx, PixelArea area = default) { var wicFrame = WicEncoderFrame; var wicRect = area.ToWicRect(); if (ctx.PlanarContext is not null) { var oformat = GUID_WICPixelFormat24bppBGR; HRESULT.Check(wicFrame->SetPixelFormat(&oformat)); using var gchY = new WeakGCHandle(ctx.PlanarContext.SourceY); using var gchCb = new WeakGCHandle(ctx.PlanarContext.SourceCb); using var gchCr = new WeakGCHandle(ctx.PlanarContext.SourceCr); var srcY = new IWICBitmapSourceImpl(gchY.Handle); var srcCb = new IWICBitmapSourceImpl(gchCb.Handle); var srcCr = new IWICBitmapSourceImpl(gchCr.Handle); var planes = stackalloc[] {&srcY, &srcCb, &srcCr }; using var pframe = default(ComPtr <IWICPlanarBitmapFrameEncode>); HRESULT.Check(wicFrame->QueryInterface(__uuidof <IWICPlanarBitmapFrameEncode>(), (void **)pframe.GetAddressOf())); HRESULT.Check(pframe.Get()->WriteSource((IWICBitmapSource **)planes, 3, area.IsEmpty ? null : &wicRect)); } else { var oformat = ctx.Source.Format.FormatGuid; HRESULT.Check(wicFrame->SetPixelFormat(&oformat)); if (oformat != ctx.Source.Format.FormatGuid) { var ptt = WICBitmapPaletteType.WICBitmapPaletteTypeCustom; using var pal = default(ComPtr <IWICPalette>); if (PixelFormat.FromGuid(oformat).NumericRepresentation == PixelNumericRepresentation.Indexed) { HRESULT.Check(Wic.Factory->CreatePalette(pal.GetAddressOf())); HRESULT.Check(pal.Get()->InitializePredefined(WICBitmapPaletteType.WICBitmapPaletteTypeFixedGray256, 0)); ptt = WICBitmapPaletteType.WICBitmapPaletteTypeFixedGray256; HRESULT.Check(wicFrame->SetPalette(pal)); } using var conv = default(ComPtr <IWICFormatConverter>); HRESULT.Check(Wic.Factory->CreateFormatConverter(conv.GetAddressOf())); HRESULT.Check(conv.Get()->Initialize(ctx.Source.AsIWICBitmapSource(ctx), &oformat, WICBitmapDitherType.WICBitmapDitherTypeNone, pal, 0.0, ptt)); ctx.Source = ctx.AddDispose(new ComPtr <IWICBitmapSource>((IWICBitmapSource *)conv.Get()).AsPixelSource(null, $"{nameof(IWICFormatConverter)}: {ctx.Source.Format.Name}->{PixelFormat.FromGuid(oformat).Name}", false)); } else if (oformat == PixelFormat.Indexed8Bpp.FormatGuid) { Debug.Assert(ctx.WicContext.DestPalette is not null); HRESULT.Check(wicFrame->SetPalette(ctx.WicContext.DestPalette)); } using var gch = new WeakGCHandle(ctx.Source); var src = new IWICBitmapSourceImpl(gch.Handle); HRESULT.Check(wicFrame->WriteSource((IWICBitmapSource *)&src, area.IsEmpty ? null : &wicRect)); } HRESULT.Check(wicFrame->Commit()); }
public void WriteSource(PipelineContext ctx, PixelArea area = default) { var wicFrame = WicEncoderFrame; var wicRect = area.ToWicRect(); if (ctx.PlanarContext is not null) { var oformat = Consts.GUID_WICPixelFormat24bppBGR; wicFrame.SetPixelFormat(ref oformat); var planes = ArrayPool <IWICBitmapSource> .Shared.Rent(3); planes[0] = ctx.PlanarContext.SourceY.AsIWICBitmapSource(); planes[1] = ctx.PlanarContext.SourceCb.AsIWICBitmapSource(); planes[2] = ctx.PlanarContext.SourceCr.AsIWICBitmapSource(); ((IWICPlanarBitmapFrameEncode)wicFrame).WriteSource(planes, 3, area.IsEmpty ? ref WICRect.Null : ref wicRect); ArrayPool <IWICBitmapSource> .Shared.Return(planes); } else { var oformat = ctx.Source.Format.FormatGuid; wicFrame.SetPixelFormat(ref oformat); if (oformat != ctx.Source.Format.FormatGuid) { var pal = default(IWICPalette); var ptt = WICBitmapPaletteType.WICBitmapPaletteTypeCustom; if (PixelFormat.FromGuid(oformat).NumericRepresentation == PixelNumericRepresentation.Indexed) { pal = ctx.WicContext.AddRef(Wic.Factory.CreatePalette()); pal.InitializePredefined(WICBitmapPaletteType.WICBitmapPaletteTypeFixedGray256, false); ptt = WICBitmapPaletteType.WICBitmapPaletteTypeFixedGray256; wicFrame.SetPalette(pal); } var conv = ctx.WicContext.AddRef(Wic.Factory.CreateFormatConverter()); conv.Initialize(ctx.Source.AsIWICBitmapSource(), oformat, WICBitmapDitherType.WICBitmapDitherTypeNone, pal, 0.0, ptt); ctx.Source = conv.AsPixelSource($"{nameof(IWICFormatConverter)}: {ctx.Source.Format.Name}->{PixelFormat.FromGuid(oformat).Name}", false); } else if (oformat == PixelFormat.Indexed8Bpp.FormatGuid) { Debug.Assert(ctx.WicContext.DestPalette is not null); wicFrame.SetPalette(ctx.WicContext.DestPalette); } wicFrame.WriteSource(ctx.Source.AsIWICBitmapSource(), area.IsEmpty ? ref WICRect.Null : ref wicRect); } wicFrame.Commit(); }
public static void AddCropper(PipelineContext ctx) { var crop = PixelArea.FromGdiRect(ctx.Settings.Crop); if (crop == ctx.Source.Area) { return; } var cropper = ctx.WicContext.AddRef(Wic.Factory.CreateBitmapClipper()); cropper.Initialize(ctx.Source.WicSource, crop.ToWicRect()); ctx.Source = cropper.AsPixelSource(nameof(IWICBitmapClipper)); ctx.Settings.Crop = ctx.Source.Area.ToGdiRect(); }
public static void AddCropper(PipelineContext ctx) { var crop = PixelArea.FromGdiRect(ctx.Settings.Crop); if (crop == ctx.Source.Area) { return; } var rect = crop.ToWicRect(); using var cropper = default(ComPtr <IWICBitmapClipper>); HRESULT.Check(Wic.Factory->CreateBitmapClipper(cropper.GetAddressOf())); HRESULT.Check(cropper.Get()->Initialize(ctx.Source.AsIWICBitmapSource(ctx), &rect)); ctx.Source = ctx.AddDispose(new ComPtr <IWICBitmapSource>((IWICBitmapSource *)cropper.Get()).AsPixelSource(ctx, nameof(IWICBitmapClipper))); ctx.Settings.Crop = ctx.Source.Area.ToGdiRect(); }
public WicImageEncoderFrame(PipelineContext ctx, WicImageEncoder encoder, PixelArea area = default) { var fmt = ctx.Settings.SaveFormat; var encArea = area.IsEmpty ? ctx.Source.Area : area; var colorMode = ctx.Settings.ColorProfileMode; var bag = default(IPropertyBag2); encoder.WicEncoder.CreateNewFrame(out var frame, ref bag); encoderFrame = ComHandle.Wrap(frame); using (var cbag = ComHandle.Wrap(bag)) { if (fmt == FileFormat.Jpeg) { bag.Write("ImageQuality", ctx.Settings.JpegQuality / 100f); } if (fmt == FileFormat.Jpeg && ctx.Settings.JpegSubsampleMode != ChromaSubsampleMode.Default) { bag.Write("JpegYCrCbSubsampling", (byte)ctx.Settings.JpegSubsampleMode); } if (fmt == FileFormat.Tiff) { bag.Write("TiffCompressionMethod", (byte)WICTiffCompressionOption.WICTiffCompressionNone); } if (fmt == FileFormat.Bmp && ctx.Source.Format.AlphaRepresentation != PixelAlphaRepresentation.None) { bag.Write("EnableV5Header32bppBGRA", true); } frame.Initialize(bag); } frame.SetSize((uint)encArea.Width, (uint)encArea.Height); frame.SetResolution(ctx.Settings.DpiX > 0d ? ctx.Settings.DpiX : ctx.ImageFrame.DpiX, ctx.Settings.DpiY > 0d ? ctx.Settings.DpiY : ctx.ImageFrame.DpiY); bool copySourceMetadata = ctx.ImageFrame is WicImageFrame srcFrame && srcFrame.WicMetadataReader is not null && ctx.Settings.MetadataNames != Enumerable.Empty <string>(); bool writeOrientation = ctx.Settings.OrientationMode == OrientationMode.Preserve && ctx.ImageFrame.ExifOrientation != Orientation.Normal; bool writeColorContext = colorMode == ColorProfileMode.NormalizeAndEmbed || colorMode == ColorProfileMode.Preserve || (colorMode == ColorProfileMode.Normalize && ctx.DestColorProfile != ColorProfile.sRGB && ctx.DestColorProfile != ColorProfile.sGrey); if ((copySourceMetadata || writeOrientation) && frame.TryGetMetadataQueryWriter(out var metawriter)) { using var cmeta = ComHandle.Wrap(metawriter); if (copySourceMetadata) { var wicFrame = (WicImageFrame)ctx.ImageFrame; foreach (string prop in ctx.Settings.MetadataNames) { if (wicFrame.WicMetadataReader !.TryGetMetadataByName(prop, out var pvar) && pvar.Value is not null) { metawriter.TrySetMetadataByName(prop, pvar); } } } if (writeOrientation) { string orientationPath = ctx.Settings.SaveFormat == FileFormat.Jpeg ? Wic.Metadata.OrientationJpeg : Wic.Metadata.OrientationExif; metawriter.TrySetMetadataByName(orientationPath, new PropVariant((ushort)ctx.ImageFrame.ExifOrientation)); } } if (writeColorContext) { Debug.Assert(ctx.WicContext.DestColorContext is not null || ctx.DestColorProfile is not null); var cc = ctx.WicContext.DestColorContext; if (ctx.DestColorProfile == ColorProfile.sRGB) { cc = WicColorProfile.SrgbCompact.Value.WicColorContext; } else if (ctx.DestColorProfile == ColorProfile.sGrey) { cc = WicColorProfile.GreyCompact.Value.WicColorContext; } else if (ctx.DestColorProfile == ColorProfile.AdobeRgb) { cc = WicColorProfile.AdobeRgb.Value.WicColorContext; } else if (ctx.DestColorProfile == ColorProfile.DisplayP3) { cc = WicColorProfile.DisplayP3Compact.Value.WicColorContext; } frame.TrySetColorContexts(cc ?? ctx.WicContext.AddRef(WicColorProfile.CreateContextFromProfile(ctx.DestColorProfile !.ProfileBytes))); } }
private static void buildPipeline(PipelineContext ctx, bool outputPlanar = true) { ctx.ImageFrame = ctx.ImageContainer.GetFrame(ctx.Settings.FrameIndex); bool processPlanar = false; var wicFrame = ctx.ImageFrame as WicImageFrame; if (wicFrame != null) { processPlanar = EnablePlanarPipeline && wicFrame.SupportsPlanarProcessing && ctx.Settings.Interpolation.WeightingFunction.Support >= 0.5; bool profilingPassThrough = processPlanar || (wicFrame.SupportsNativeScale && ctx.Settings.HybridScaleRatio > 1); ctx.Source = wicFrame.WicSource.AsPixelSource(nameof(IWICBitmapFrameDecode), !profilingPassThrough); } else if (ctx.ImageFrame is IYccImageFrame planarFrame) { processPlanar = true; outputPlanar = outputPlanar && planarFrame.IsFullRange && planarFrame.RgbYccMatrix.IsRouglyEqualTo(YccMatrix.Rec601); ctx.PlanarContext = new PipelineContext.PlanarPipelineContext(planarFrame.PixelSource.AsPixelSource(), planarFrame.PixelSourceCb.AsPixelSource(), planarFrame.PixelSourceCr.AsPixelSource()); ctx.Source = ctx.PlanarContext.SourceY; } MagicTransforms.AddColorProfileReader(ctx); ctx.FinalizeSettings(); ctx.Settings.UnsharpMask = ctx.UsedSettings.UnsharpMask; ctx.Settings.JpegQuality = ctx.UsedSettings.JpegQuality; ctx.Settings.JpegSubsampleMode = ctx.UsedSettings.JpegSubsampleMode; var subsample = ctx.Settings.JpegSubsampleMode; if (processPlanar) { if (wicFrame != null && !ctx.Settings.AutoCrop && ctx.Settings.HybridScaleRatio == 1) { var orCrop = PixelArea.FromGdiRect(ctx.Settings.Crop).DeOrient(ctx.Orientation, ctx.Source.Width, ctx.Source.Height); if (wicFrame.ChromaSubsampling.IsSubsampledX() && ((orCrop.X & 1) != 0 || (orCrop.Width & 1) != 0)) { processPlanar = false; } if (wicFrame.ChromaSubsampling.IsSubsampledY() && ((orCrop.Y & 1) != 0 || (orCrop.Height & 1) != 0)) { processPlanar = false; } } if (ctx.Settings.SaveFormat == FileFormat.Jpeg && ctx.Orientation.SwapsDimensions()) { if (subsample.IsSubsampledX() && (ctx.Settings.InnerSize.Width & 1) != 0) { outputPlanar = false; } if (subsample.IsSubsampledY() && (ctx.Settings.InnerSize.Height & 1) != 0) { outputPlanar = false; } } } if (processPlanar) { bool savePlanar = outputPlanar && ctx.Settings.SaveFormat == FileFormat.Jpeg && ctx.Settings.OuterSize == ctx.Settings.InnerSize && ctx.DestColorProfile == ctx.SourceColorProfile; if (wicFrame != null) { WicTransforms.AddPlanarCache(ctx); } MagicTransforms.AddPlanarCropper(ctx); MagicTransforms.AddPlanarHybridScaler(ctx); MagicTransforms.AddPlanarHighQualityScaler(ctx, savePlanar ? subsample : ChromaSubsampleMode.Subsample444); MagicTransforms.AddUnsharpMask(ctx); if (savePlanar) { MagicTransforms.AddPlanarExifFlipRotator(ctx); MagicTransforms.AddPlanarExternalFormatConverter(ctx); } else { MagicTransforms.AddPlanarConverter(ctx); MagicTransforms.AddColorspaceConverter(ctx); MagicTransforms.AddExifFlipRotator(ctx); MagicTransforms.AddPad(ctx); } } else { WicTransforms.AddNativeScaler(ctx); MagicTransforms.AddCropper(ctx); MagicTransforms.AddHybridScaler(ctx); WicTransforms.AddPixelFormatConverter(ctx); MagicTransforms.AddHybridScaler(ctx); MagicTransforms.AddHighQualityScaler(ctx); MagicTransforms.AddColorspaceConverter(ctx); MagicTransforms.AddMatte(ctx); MagicTransforms.AddUnsharpMask(ctx); MagicTransforms.AddExifFlipRotator(ctx); MagicTransforms.AddPad(ctx); } }
public WicImageEncoderFrame(PipelineContext ctx, WicImageEncoder encoder, PixelArea area = default) { var fmt = ctx.Settings.SaveFormat; var encArea = area.IsEmpty ? ctx.Source.Area : area; var colorMode = ctx.Settings.ColorProfileMode; using var frame = default(ComPtr <IWICBitmapFrameEncode>); using (var pbag = default(ComPtr <IPropertyBag2>)) { HRESULT.Check(encoder.WicEncoder->CreateNewFrame(frame.GetAddressOf(), pbag.GetAddressOf())); if (fmt == FileFormat.Jpeg) { pbag.Write("ImageQuality", ctx.Settings.JpegQuality / 100f); } if (fmt == FileFormat.Jpeg && ctx.Settings.JpegSubsampleMode != ChromaSubsampleMode.Default) { pbag.Write("JpegYCrCbSubsampling", (byte)ctx.Settings.JpegSubsampleMode); } if (fmt == FileFormat.Tiff) { pbag.Write("TiffCompressionMethod", (byte)WICTiffCompressionOption.WICTiffCompressionNone); } if (fmt == FileFormat.Bmp && ctx.Source.Format.AlphaRepresentation != PixelAlphaRepresentation.None) { pbag.Write("EnableV5Header32bppBGRA", true); } HRESULT.Check(frame.Get()->Initialize(pbag)); } HRESULT.Check(frame.Get()->SetSize((uint)encArea.Width, (uint)encArea.Height)); HRESULT.Check(frame.Get()->SetResolution(ctx.Settings.DpiX > 0d ? ctx.Settings.DpiX : ctx.ImageFrame.DpiX, ctx.Settings.DpiY > 0d ? ctx.Settings.DpiY : ctx.ImageFrame.DpiY)); bool copySourceMetadata = ctx.ImageFrame is WicImageFrame srcFrame && srcFrame.WicMetadataReader is not null && ctx.Settings.MetadataNames != Enumerable.Empty <string>(); bool writeOrientation = ctx.Settings.OrientationMode == OrientationMode.Preserve && ctx.ImageFrame.ExifOrientation != Orientation.Normal; bool writeColorContext = colorMode == ColorProfileMode.NormalizeAndEmbed || colorMode == ColorProfileMode.Preserve || (colorMode == ColorProfileMode.Normalize && ctx.DestColorProfile != ColorProfile.sRGB && ctx.DestColorProfile != ColorProfile.sGrey); using var metawriter = default(ComPtr <IWICMetadataQueryWriter>); if ((copySourceMetadata || writeOrientation) && SUCCEEDED(frame.Get()->GetMetadataQueryWriter(metawriter.GetAddressOf()))) { if (copySourceMetadata) { var wicFrame = (WicImageFrame)ctx.ImageFrame; foreach (string prop in ctx.Settings.MetadataNames) { var pv = default(PROPVARIANT); if (SUCCEEDED(wicFrame.WicMetadataReader->GetMetadataByName(prop, &pv)) && pv.vt != (ushort)VARENUM.VT_EMPTY) { _ = metawriter.Get()->SetMetadataByName(prop, &pv); } } } if (writeOrientation) { string orientationPath = ctx.Settings.SaveFormat == FileFormat.Jpeg ? Wic.Metadata.OrientationJpeg : Wic.Metadata.OrientationExif; var pv = new PROPVARIANT { vt = (ushort)VARENUM.VT_UI2 }; pv.Anonymous.uiVal = (ushort)ctx.ImageFrame.ExifOrientation; _ = metawriter.Get()->SetMetadataByName(orientationPath, &pv); } } if (writeColorContext) { Debug.Assert(ctx.WicContext.DestColorContext is not null || ctx.DestColorProfile is not null); var cc = ctx.WicContext.DestColorContext; if (ctx.DestColorProfile == ColorProfile.sRGB) { cc = WicColorProfile.SrgbCompact.Value.WicColorContext; } else if (ctx.DestColorProfile == ColorProfile.sGrey) { cc = WicColorProfile.GreyCompact.Value.WicColorContext; } else if (ctx.DestColorProfile == ColorProfile.AdobeRgb) { cc = WicColorProfile.AdobeRgb.Value.WicColorContext; } else if (ctx.DestColorProfile == ColorProfile.DisplayP3) { cc = WicColorProfile.DisplayP3Compact.Value.WicColorContext; } using var ccc = default(ComPtr <IWICColorContext>); if (cc is null) { ccc.Attach(WicColorProfile.CreateContextFromProfile(ctx.DestColorProfile !.ProfileBytes)); cc = ccc; } _ = frame.Get()->SetColorContexts(1, &cc); } WicEncoderFrame = frame.Detach(); }
public WicImageEncoderFrame(PipelineContext ctx, WicImageEncoder encoder, PixelArea area = default) { var fmt = ctx.Settings.SaveFormat; var encArea = area.IsEmpty ? ctx.Source.Area : area; var colorMode = ctx.Settings.ColorProfileMode; using var frame = default(ComPtr <IWICBitmapFrameEncode>); using (var pbag = default(ComPtr <IPropertyBag2>)) { HRESULT.Check(encoder.WicEncoder->CreateNewFrame(frame.GetAddressOf(), pbag.GetAddressOf())); if (fmt == FileFormat.Jpeg) { pbag.Write("ImageQuality", ctx.Settings.JpegQuality / 100f); } if (fmt == FileFormat.Jpeg && ctx.Settings.JpegSubsampleMode != ChromaSubsampleMode.Default) { pbag.Write("JpegYCrCbSubsampling", (byte)ctx.Settings.JpegSubsampleMode); } if (fmt == FileFormat.Tiff) { pbag.Write("TiffCompressionMethod", (byte)WICTiffCompressionOption.WICTiffCompressionNone); } if (fmt == FileFormat.Bmp && ctx.Source.Format.AlphaRepresentation != PixelAlphaRepresentation.None) { pbag.Write("EnableV5Header32bppBGRA", true); } HRESULT.Check(frame.Get()->Initialize(pbag)); } HRESULT.Check(frame.Get()->SetSize((uint)encArea.Width, (uint)encArea.Height)); HRESULT.Check(frame.Get()->SetResolution(ctx.Settings.DpiX > 0d ? ctx.Settings.DpiX : ctx.ImageFrame.DpiX, ctx.Settings.DpiY > 0d ? ctx.Settings.DpiY : ctx.ImageFrame.DpiY)); bool copySourceMetadata = ctx.ImageFrame is WicImageFrame srcFrame && srcFrame.WicMetadataReader is not null && ctx.Settings.MetadataNames != Enumerable.Empty <string>(); bool writeOrientation = ctx.Settings.OrientationMode == OrientationMode.Preserve && ctx.ImageFrame.ExifOrientation != Orientation.Normal; bool writeColorContext = colorMode == ColorProfileMode.NormalizeAndEmbed || colorMode == ColorProfileMode.Preserve || (colorMode == ColorProfileMode.Normalize && ctx.DestColorProfile != ColorProfile.sRGB && ctx.DestColorProfile != ColorProfile.sGrey); using var metawriter = default(ComPtr <IWICMetadataQueryWriter>); if ((copySourceMetadata || writeOrientation) && SUCCEEDED(frame.Get()->GetMetadataQueryWriter(metawriter.GetAddressOf()))) { if (copySourceMetadata) { var wicFrame = (WicImageFrame)ctx.ImageFrame; foreach (string prop in ctx.Settings.MetadataNames) { var pv = default(PROPVARIANT); if (SUCCEEDED(wicFrame.WicMetadataReader->GetMetadataByName(prop, &pv)) && pv.vt != (ushort)VARENUM.VT_EMPTY) { _ = metawriter.Get()->SetMetadataByName(prop, &pv); } } } if (writeOrientation) { string orientationPath = ctx.Settings.SaveFormat == FileFormat.Jpeg ? Wic.Metadata.OrientationJpeg : Wic.Metadata.OrientationExif; var pv = new PROPVARIANT { vt = (ushort)VARENUM.VT_UI2 }; pv.Anonymous.uiVal = (ushort)ctx.ImageFrame.ExifOrientation; _ = metawriter.Get()->SetMetadataByName(orientationPath, &pv); } } if (writeColorContext) { Debug.Assert(ctx.WicContext.DestColorContext is not null || ctx.DestColorProfile is not null); var cc = ctx.WicContext.DestColorContext; if (ctx.DestColorProfile == ColorProfile.sRGB) { cc = WicColorProfile.SrgbCompact.Value.WicColorContext; } else if (ctx.DestColorProfile == ColorProfile.sGrey) { cc = WicColorProfile.GreyCompact.Value.WicColorContext; } else if (ctx.DestColorProfile == ColorProfile.AdobeRgb) { cc = WicColorProfile.AdobeRgb.Value.WicColorContext; } else if (ctx.DestColorProfile == ColorProfile.DisplayP3) { cc = WicColorProfile.DisplayP3Compact.Value.WicColorContext; } // WIC writes gAMA and cHRM tags along with iCCP when SetColorContexts is called on a PNG frame. // Chromium ignores the iCCP tag if the others are present, so we try to write it alone explicitly. if (fmt == FileFormat.Png && ctx.DestColorProfile is not null && metawriter.Get() is not null) { var name = (ReadOnlySpan <byte>) new[] { (byte)'I', (byte)'C', (byte)'C', (byte)'\0' }; fixed(byte *pprofile = ctx.DestColorProfile.ProfileBytes) { var pvn = new PROPVARIANT { vt = (ushort)VARENUM.VT_LPSTR }; pvn.Anonymous.pszVal = (sbyte *)Unsafe.AsPointer(ref MemoryMarshal.GetReference(name)); _ = metawriter.Get()->SetMetadataByName(Wic.Metadata.Png.IccProfileName, &pvn); var pvv = new PROPVARIANT { vt = (ushort)(VARENUM.VT_UI1 | VARENUM.VT_VECTOR) }; pvv.Anonymous.blob.pBlobData = pprofile; pvv.Anonymous.blob.cbSize = (uint)ctx.DestColorProfile.ProfileBytes.Length; _ = metawriter.Get()->SetMetadataByName(Wic.Metadata.Png.IccProfileData, &pvv); } } else { using var ccc = default(ComPtr <IWICColorContext>); if (cc is null) { ccc.Attach(WicColorProfile.CreateContextFromProfile(ctx.DestColorProfile !.ProfileBytes)); cc = ccc; } _ = frame.Get()->SetColorContexts(1, &cc); } }