aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/Drawing/ImageProcessor.cs
blob: b7815750b5201c31abc052ae77a487dcdb6d0308 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;

namespace MediaBrowser.Controller.Drawing
{
    public static class ImageProcessor
    {
        /// <summary>
        /// Processes an image by resizing to target dimensions
        /// </summary>
        /// <param name="sourceImageStream">The stream containing the source image</param>
        /// <param name="toStream">The stream to save the new image to</param>
        /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
        /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
        /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
        /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
        /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
        /// <param name="entity">The entity that owns the image</param>
        /// <param name="imageType">The image type</param>
        /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
        public static void ProcessImage(Stream sourceImageStream, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality, BaseEntity entity, ImageType imageType, int imageIndex)
        {
            Image originalImage = Image.FromStream(sourceImageStream);

            // Determine the output size based on incoming parameters
            Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight);

            Bitmap thumbnail;

            // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
            if (originalImage.PixelFormat.HasFlag(PixelFormat.Indexed))
            {
                thumbnail = new Bitmap(originalImage, newSize.Width, newSize.Height);
            }
            else
            {
                thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat);
            }

            // Preserve the original resolution
            thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);

            Graphics thumbnailGraph = Graphics.FromImage(thumbnail);

            thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality;
            thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
            thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
            thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
            thumbnailGraph.CompositingMode = CompositingMode.SourceOver;

            thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height);

            // Run Kernel image processors
            if (Kernel.Instance.ImageProcessors.Any())
            {
                ExecuteAdditionalImageProcessors(thumbnail, thumbnailGraph, entity, imageType, imageIndex);
            }

            // Write to the output stream
            SaveImage(originalImage.RawFormat, thumbnail, toStream, quality);

            thumbnailGraph.Dispose();
            thumbnail.Dispose();
            originalImage.Dispose();
        }

        /// <summary>
        /// Executes additional image processors that are registered with the Kernel
        /// </summary>
        /// <param name="bitmap">The bitmap holding the original image, after re-sizing</param>
        /// <param name="graphics">The graphics surface on which the output is drawn</param>
        /// <param name="entity">The entity that owns the image</param>
        /// <param name="imageType">The image type</param>
        /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
        private static void ExecuteAdditionalImageProcessors(Bitmap bitmap, Graphics graphics, BaseEntity entity, ImageType imageType, int imageIndex)
        {
            var baseItem = entity as BaseItem;

            if (baseItem != null)
            {
                foreach (var processor in Kernel.Instance.ImageProcessors)
                {
                    processor.ProcessImage(bitmap, graphics, baseItem, imageType, imageIndex);
                }
            }
            else
            {
                foreach (var processor in Kernel.Instance.ImageProcessors)
                {
                    processor.ProcessImage(bitmap, graphics, entity);
                }
            }
        }

        public static void SaveImage(ImageFormat originalImageRawFormat, Image newImage, Stream toStream, int? quality)
        {
            // Use special save methods for jpeg and png that will result in a much higher quality image
            // All other formats use the generic Image.Save
            if (ImageFormat.Jpeg.Equals(originalImageRawFormat))
            {
                SaveJpeg(newImage, toStream, quality);
            }
            else if (ImageFormat.Png.Equals(originalImageRawFormat))
            {
                newImage.Save(toStream, ImageFormat.Png);
            }
            else
            {
                newImage.Save(toStream, originalImageRawFormat);
            }
        }

        public static void SaveJpeg(Image image, Stream target, int? quality)
        {
            if (!quality.HasValue)
            {
                quality = 90;
            }

            using (var encoderParameters = new EncoderParameters(1))
            {
                encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value);
                image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters);
            }
        }

        public static ImageCodecInfo GetImageCodecInfo(string mimeType)
        {
            ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders();

            for (int i = 0; i < info.Length; i++)
            {
                ImageCodecInfo ici = info[i];
                if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase))
                {
                    return ici;
                }
            }
            return info[1];
        }
    }
}