saucecontrol / PhotoSauce

MagicScaler high-performance, high-quality image processing pipeline for .NET
http://photosauce.net/
MIT License
569 stars 49 forks source link

Transparent Color on PadTransform #120

Open paul-van-der-hoeven opened 10 months ago

paul-van-der-hoeven commented 10 months ago

The padding Color.Transparent is inconsistent between the two methods:

using var outStreamF_PNG = new FileStream(StorePathFile_PNG, FileMode.OpenOrCreate);
var padSettings_PNG = new ProcessImageSettings { EncoderOptions = new PngEncoderOptions { Filter = PngFilter.Adaptive } };
MagicImageProcessor.BuildPipeline(mStream_PNG, padSettings_PNG).AddTransform(new PadTransform(System.Drawing.Color.Transparent, padTop, padRight, padBottom, padLeft)).WriteOutput(outStreamF_PNG);

Despite the instruction of System.Drawing.Color.Transparent, the padded area is rendered as white.

Whereas setting the ProcessImageSettings with MatteColor = System.Drawing.Color.Transparent will produce a transparent padded area around the image.

ProcessImageSettings processImgSettings = new()
{
    HybridMode = HybridScaleMode.FavorQuality,
    Width = destF.MaxWidth,
    Height = destF.MaxHeight,
    Sharpen = true,
    ResizeMode = CropScaleMode.Pad,
    MatteColor = System.Drawing.Color.Transparent
};

processImgSettings.EncoderOptions = new PngEncoderOptions { Filter = PngFilter.Adaptive };
MagicImageProcessor.ProcessImage(btArr, StorePathFile_PNG, processImgSettings);

I noticed that the documentation for PadTransform says "Adds solid-colored padding pixels to an image." But why would this not honor System.Drawing.Color.Transparent when padding a png image?

When we process images that are larger than the target size, the ProcessImageSettings with CropScaleMod.Pad works perfectly. But when the source image is smaller than the target size we have to calculate the padding for each side and apply the PadTransform. This is where it fails us. A cropscale mode for padding with the option to not upscale the image would be ideal, but would gladly settle for a solution where the PadTransform option works with Color.Transparent.

saucecontrol commented 10 months ago

Howdy! Thanks for the detailed write-up.

The PadTransform behavior you're seeing is intentional, as ProcessingPipeline (open pipeline) is meant to be a lower-level API than the all-in-one ProcessImage (closed pipeline). The format of the open pipeline's IPixelSource will attempt to match the input image as closely as possible, while giving you the flexibility to change it if you prefer.

So, if you start with a source that does not contain transparency, the pipeline's output IPixelSource will also not have transparency, and the alpha channel of the PadTransform's matte color will be ignored. Same would happen if you started with a source image in a greyscale pixel format -- a matte color that is not grey would have its additional color channels ignored, keeping only one channel (blue) as the grey value.

Here's an example including the format conversion explicitly, which should give your desired result:

var settings = ProcessImageSettings.Default;
_ = settings.TrySetEncoderFormat(ImageMimeTypes.Png);

using var outStreamF_PNG = File.Create(StorePathFile_PNG);
using var pipeline = MagicImageProcessor.BuildPipeline(mStream_PNG, settings);
pipeline
    .AddTransform(new FormatConversionTransform(PixelFormats.Bgra32bpp))
    .AddTransform(new PadTransform(System.Drawing.Color.Transparent, padTop, padRight, padBottom, padLeft))
    .WriteOutput(outStreamF_PNG);

Note that adding the FormatConversionTransform will be a no-op if the format is already the one you are requesting. There is a limitation currently that you can't request the format of the current IPixelSource and then modify the pipeline after, so this is the best way to handle format if you're unsure what you have. I will address that limitation in a future version.

Also be aware ProcessingPipeline needs to be disposed, as it holds on to a number of unmanaged codec resources. Kind of messes with the fluent API, but it's important.

And finally, I agree an additional CropScaleMode (PadOnly, maybe?) would probably be useful, so I'll look at that for next release.