Exemple #1
0
        /// <summary>
        /// Processes an image by resizing to target dimensions
        /// </summary>
        /// <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>
        /// <param name="originalImagePath">The original image path.</param>
        /// <param name="cropWhitespace">if set to <c>true</c> [crop whitespace].</param>
        /// <param name="dateModified">The last date modified of the original image file</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="enhancers">The enhancers.</param>
        /// <returns>Task.</returns>
        /// <exception cref="System.ArgumentNullException">entity</exception>
        public async Task ProcessImage(BaseItem entity, ImageType imageType, int imageIndex, string originalImagePath, bool cropWhitespace, DateTime dateModified, Stream toStream, int?width, int?height, int?maxWidth, int?maxHeight, int?quality, List <IImageEnhancer> enhancers)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }

            if (toStream == null)
            {
                throw new ArgumentNullException("toStream");
            }

            if (cropWhitespace)
            {
                originalImagePath = await GetCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
            }

            // No enhancement - don't cache
            if (enhancers.Count > 0)
            {
                try
                {
                    // Enhance if we have enhancers
                    var ehnancedImagePath = await GetEnhancedImage(originalImagePath, dateModified, entity, imageType, imageIndex, enhancers).ConfigureAwait(false);

                    // If the path changed update dateModified
                    if (!ehnancedImagePath.Equals(originalImagePath, StringComparison.OrdinalIgnoreCase))
                    {
                        dateModified      = File.GetLastWriteTimeUtc(ehnancedImagePath);
                        originalImagePath = ehnancedImagePath;
                    }
                }
                catch (Exception ex)
                {
                    _logger.Error("Error enhancing image", ex);
                }
            }

            var originalImageSize = await GetImageSize(originalImagePath, dateModified).ConfigureAwait(false);

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

            if (!quality.HasValue)
            {
                quality = 90;
            }

            var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality.Value, dateModified);

            try
            {
                using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
                {
                    await fileStream.CopyToAsync(toStream).ConfigureAwait(false);

                    return;
                }
            }
            catch (IOException)
            {
                // Cache file doesn't exist or is currently being written ro
            }

            var semaphore = GetLock(cacheFilePath);

            await semaphore.WaitAsync().ConfigureAwait(false);

            // Check again in case of lock contention
            if (File.Exists(cacheFilePath))
            {
                try
                {
                    using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
                    {
                        await fileStream.CopyToAsync(toStream).ConfigureAwait(false);

                        return;
                    }
                }
                finally
                {
                    semaphore.Release();
                }
            }

            try
            {
                using (var fileStream = new FileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
                {
                    // Copy to memory stream to avoid Image locking file
                    using (var memoryStream = new MemoryStream())
                    {
                        await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);

                        using (var originalImage = Image.FromStream(memoryStream, true, false))
                        {
                            var newWidth  = Convert.ToInt32(newSize.Width);
                            var newHeight = Convert.ToInt32(newSize.Height);

                            // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
                            using (var thumbnail = !ImageExtensions.IsPixelFormatSupportedByGraphicsObject(originalImage.PixelFormat) ? new Bitmap(originalImage, newWidth, newHeight) : new Bitmap(newWidth, newHeight, originalImage.PixelFormat))
                            {
                                // Preserve the original resolution
                                thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);

                                using (var 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, newWidth, newHeight);

                                    var outputFormat = originalImage.RawFormat;

                                    using (var outputMemoryStream = new MemoryStream())
                                    {
                                        // Save to the memory stream
                                        thumbnail.Save(outputFormat, outputMemoryStream, quality.Value);

                                        var bytes = outputMemoryStream.ToArray();

                                        var outputTask = toStream.WriteAsync(bytes, 0, bytes.Length);

                                        // kick off a task to cache the result
                                        var cacheTask = CacheResizedImage(cacheFilePath, bytes);

                                        await Task.WhenAll(outputTask, cacheTask).ConfigureAwait(false);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            finally
            {
                semaphore.Release();
            }
        }
        /// <summary>
        /// Processes an image by resizing to target dimensions
        /// </summary>
        /// <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>
        /// <param name="cropWhitespace">if set to <c>true</c> [crop whitespace].</param>
        /// <param name="dateModified">The last date modified of the original image file</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>
        /// <returns>Task.</returns>
        /// <exception cref="System.ArgumentNullException">entity</exception>
        public async Task ProcessImage(BaseItem entity, ImageType imageType, int imageIndex, bool cropWhitespace, DateTime dateModified, Stream toStream, int?width, int?height, int?maxWidth, int?maxHeight, int?quality)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }

            if (toStream == null)
            {
                throw new ArgumentNullException("toStream");
            }

            var originalImagePath = GetImagePath(entity, imageType, imageIndex);

            if (cropWhitespace)
            {
                try
                {
                    originalImagePath = await GetCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    // We have to have a catch-all here because some of the .net image methods throw a plain old Exception
                    _logger.ErrorException("Error cropping image", ex);
                }
            }

            try
            {
                // Enhance if we have enhancers
                var ehnancedImagePath = await GetEnhancedImage(originalImagePath, dateModified, entity, imageType, imageIndex).ConfigureAwait(false);

                // If the path changed update dateModified
                if (!ehnancedImagePath.Equals(originalImagePath, StringComparison.OrdinalIgnoreCase))
                {
                    dateModified      = File.GetLastWriteTimeUtc(ehnancedImagePath);
                    originalImagePath = ehnancedImagePath;
                }
            }
            catch
            {
                _logger.Error("Error enhancing image");
            }

            var originalImageSize = await GetImageSize(originalImagePath, dateModified).ConfigureAwait(false);

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

            if (!quality.HasValue)
            {
                quality = 90;
            }

            var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality.Value, dateModified);

            // Grab the cache file if it already exists
            try
            {
                using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
                {
                    await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
                }
                return;
            }
            catch (FileNotFoundException)
            {
                // Cache file doesn't exist. No biggie.
            }

            using (var fileStream = File.OpenRead(originalImagePath))
            {
                using (var originalImage = Bitmap.FromStream(fileStream, true, false))
                {
                    var newWidth  = Convert.ToInt32(newSize.Width);
                    var newHeight = Convert.ToInt32(newSize.Height);

                    // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
                    var thumbnail = !ImageExtensions.IsPixelFormatSupportedByGraphicsObject(originalImage.PixelFormat) ? new Bitmap(originalImage, newWidth, newHeight) : new Bitmap(newWidth, newHeight, originalImage.PixelFormat);

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

                    var 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, newWidth, newHeight);

                    var outputFormat = originalImage.RawFormat;

                    using (var memoryStream = new MemoryStream {
                    })
                    {
                        // Save to the memory stream
                        thumbnail.Save(outputFormat, memoryStream, quality.Value);

                        var bytes = memoryStream.ToArray();

                        var outputTask = Task.Run(async() => await toStream.WriteAsync(bytes, 0, bytes.Length));

                        // Save to the cache location
                        using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
                        {
                            // Save to the filestream
                            await cacheFileStream.WriteAsync(bytes, 0, bytes.Length);
                        }

                        await outputTask.ConfigureAwait(false);
                    }

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