public async Task <UploadResult> ProcessFile( IFormFile formFile, ImageProcessingOptions options, bool?resizeImages, int?maxWidth, int?maxHeight, string requestedVirtualPath = "", string newFileName = "", bool allowRootPath = true, bool createThumbnail = false, bool?keepOriginal = null ) { await EnsureProjectSettings().ConfigureAwait(false); string currentFsPath = _rootPath.RootFileSystemPath; string currentVirtualPath = _rootPath.RootVirtualPath; string[] virtualSegments = options.ImageDefaultVirtualSubPath.Split('/'); bool doResize = resizeImages ?? options.AutoResize; if ((!string.IsNullOrEmpty(requestedVirtualPath)) && (requestedVirtualPath.StartsWith(_rootPath.RootVirtualPath))) { var virtualSubPath = requestedVirtualPath.Substring(_rootPath.RootVirtualPath.Length); var segments = virtualSubPath.Split('/'); if (segments.Length > 0) { var requestedFsPath = Path.Combine(_rootPath.RootFileSystemPath, Path.Combine(segments)); if (!Directory.Exists(requestedFsPath)) { //_log.LogError("directory not found for currentPath " + requestedFsPath); // user has file system permission and could manually create the needed folder so auto ensure // since it is a sub path of the root EnsureSubFolders(_rootPath.RootFileSystemPath, segments); } currentVirtualPath = requestedVirtualPath; virtualSegments = segments; currentFsPath = Path.Combine(currentFsPath, Path.Combine(virtualSegments)); } } else { // ensure the folders if no currentDir provided, // if it is provided it must be an existing path // options.ImageDefaultVirtualSubPath might not exist on first upload so need to ensure it if (!allowRootPath) { currentVirtualPath = currentVirtualPath + options.ImageDefaultVirtualSubPath; currentFsPath = Path.Combine(currentFsPath, Path.Combine(virtualSegments)); EnsureSubFolders(_rootPath.RootFileSystemPath, virtualSegments); } } string newName; if (!string.IsNullOrEmpty(newFileName)) { newName = _nameRules.GetCleanFileName(newFileName); } else { newName = _nameRules.GetCleanFileName(Path.GetFileName(formFile.FileName)); } var newUrl = currentVirtualPath + "/" + newName; var fsPath = Path.Combine(currentFsPath, newName); var ext = Path.GetExtension(newName); var webSizeName = Path.GetFileNameWithoutExtension(newName) + "-ws" + ext; string webUrl = currentVirtualPath + "/" + webSizeName; var thumbSizeName = Path.GetFileNameWithoutExtension(newName) + "-thumb" + ext; string thumbUrl = currentVirtualPath + "/" + thumbSizeName; var didResize = false; var didCreateThumb = false; try { using (var stream = new FileStream(fsPath, FileMode.Create)) { await formFile.CopyToAsync(stream); } var mimeType = GetMimeType(ext); if ((doResize) && IsWebImageFile(ext)) { int resizeWidth = GetMaxWidth(maxWidth, options); int resizeHeight = GetMaxWidth(maxHeight, options); didResize = _imageResizer.ResizeImage( fsPath, currentFsPath, webSizeName, mimeType, resizeWidth, resizeHeight, options.AllowEnlargement, options.ResizeQuality ); } if (createThumbnail) { didCreateThumb = _imageResizer.ResizeImage( fsPath, currentFsPath, thumbSizeName, mimeType, options.ThumbnailImageMaxWidth, options.ThumbnailImageMaxHeight, false, options.ResizeQuality ); } if (didResize) { if (keepOriginal.HasValue) { if (keepOriginal.Value == false) { File.Delete(fsPath); newUrl = string.Empty; } } else if (!options.KeepOriginalImages) // use default if not explcitely passed { File.Delete(fsPath); newUrl = string.Empty; } } } catch (Exception ex) { _log.LogError($"{ex.Message}:{ex.StackTrace}"); return(new UploadResult { ErrorMessage = _sr["There was an error logged during file processing"] }); } return(new UploadResult { OriginalUrl = newUrl, ResizedUrl = didResize ? webUrl : string.Empty, ThumbUrl = didCreateThumb ? thumbUrl : string.Empty, Name = newName, Length = formFile.Length, Type = formFile.ContentType }); }
private Tuple <ImageSize, bool> GetNewImageSize(string originalImagePath, DateTime dateModified, ImageProcessingOptions options) { try { var originalImageSize = GetImageSize(originalImagePath, dateModified, true); // Determine the output size based on incoming parameters var newSize = DrawingUtils.Resize(originalImageSize, options.Width, options.Height, options.MaxWidth, options.MaxHeight); return(new Tuple <ImageSize, bool>(newSize, !newSize.Equals(originalImageSize))); } catch { return(new Tuple <ImageSize, bool>(GetSizeEstimage(options), true)); } }
public async Task <UploadResult> CropFile( ImageProcessingOptions options, string sourceFilePath, int offsetX, int offsetY, int widthToCrop, int heightToCrop, int finalWidth, int finalHeight ) { if (string.IsNullOrWhiteSpace(sourceFilePath)) { _log.LogError($"sourceFilePath not provided for crop"); return(new UploadResult { ErrorMessage = _sr["There was an error logged during file processing"] }); } await EnsureProjectSettings().ConfigureAwait(false); string currentFsPath = _rootPath.RootFileSystemPath; string currentVirtualPath = _rootPath.RootVirtualPath; string[] virtualSegments = options.ImageDefaultVirtualSubPath.Split('/'); if (!sourceFilePath.StartsWith(_rootPath.RootVirtualPath)) { _log.LogError($"{sourceFilePath} not a sub path of root path {_rootPath.RootVirtualPath}"); return(new UploadResult { ErrorMessage = _sr["There was an error logged during file processing"] }); } var fileToCropName = Path.GetFileName(sourceFilePath); var fileToCropNameWithooutExtenstion = Path.GetFileNameWithoutExtension(sourceFilePath); var ext = Path.GetExtension(sourceFilePath); var mimeType = GetMimeType(ext); var isImage = IsWebImageFile(ext); if (!isImage) { _log.LogError($"{sourceFilePath} is not not an image file"); return(new UploadResult { ErrorMessage = _sr["There was an error logged during file processing"] }); } var fileToCropFolderVPath = sourceFilePath.Replace(fileToCropName, ""); var virtualSubPath = fileToCropFolderVPath.Substring(_rootPath.RootVirtualPath.Length); var segments = virtualSubPath.Split('/'); if (segments.Length <= 0) { _log.LogError($"{sourceFilePath} not found"); return(new UploadResult { ErrorMessage = _sr["There was an error logged during file processing"] }); } var requestedFsPath = Path.Combine(_rootPath.RootFileSystemPath, Path.Combine(segments)); if (!Directory.Exists(requestedFsPath)) { _log.LogError("directory not found for currentPath " + requestedFsPath); return(new UploadResult { ErrorMessage = _sr["There was an error logged during file processing"] }); } currentVirtualPath = virtualSubPath; virtualSegments = segments; currentFsPath = Path.Combine(currentFsPath, Path.Combine(virtualSegments)); var sourceFsPath = Path.Combine(currentFsPath, fileToCropName); var cropNameSegment = "-crop"; int previousCropCount = 0; var targetFsPath = Path.Combine(currentFsPath, fileToCropNameWithooutExtenstion + cropNameSegment + ext); while (File.Exists(targetFsPath)) { previousCropCount += 1; targetFsPath = Path.Combine(currentFsPath, fileToCropNameWithooutExtenstion + cropNameSegment + previousCropCount.ToString() + ext); } ; var didCrop = _imageResizer.CropExistingImage( sourceFsPath, targetFsPath, offsetX, offsetY, widthToCrop, heightToCrop, finalWidth, finalHeight ); if (!didCrop) { _log.LogError($"failed to crop image {requestedFsPath}"); return(new UploadResult { ErrorMessage = _sr["There was an error logged during file processing"] }); } return(new UploadResult { OriginalUrl = sourceFilePath, ResizedUrl = currentVirtualPath + Path.GetFileName(targetFsPath) }); }
/// <summary> /// Draws the indicator. /// </summary> /// <param name="graphics">The graphics.</param> /// <param name="imageWidth">Width of the image.</param> /// <param name="imageHeight">Height of the image.</param> /// <param name="options">The options.</param> private void DrawIndicator(Graphics graphics, int imageWidth, int imageHeight, ImageProcessingOptions options) { if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && options.PercentPlayed.Equals(0)) { return; } try { if (options.AddPlayedIndicator) { var currentImageSize = new Size(imageWidth, imageHeight); new PlayedIndicatorDrawer().DrawPlayedIndicator(graphics, currentImageSize); } else if (options.UnplayedCount.HasValue) { var currentImageSize = new Size(imageWidth, imageHeight); new UnplayedCountIndicator().DrawUnplayedCountIndicator(graphics, currentImageSize, options.UnplayedCount.Value); } if (options.PercentPlayed > 0) { var currentImageSize = new Size(imageWidth, imageHeight); new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed); } } catch (Exception ex) { _logger.ErrorException("Error drawing indicator overlay", ex); } }
public async Task <Tuple <string, string> > ProcessImage(ImageProcessingOptions options) { if (options == null) { throw new ArgumentNullException("options"); } var originalImage = options.Image; if (!originalImage.IsLocalFile) { originalImage = await _libraryManager().ConvertImageToLocal(options.Item, originalImage, options.ImageIndex).ConfigureAwait(false); } var originalImagePath = originalImage.Path; if (!_imageEncoder.SupportsImageEncoding) { return(new Tuple <string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath))); } var dateModified = originalImage.DateModified; if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding) { var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } if (options.Enhancers.Count > 0) { var tuple = await GetEnhancedImage(new ItemImageInfo { DateModified = dateModified, Type = originalImage.Type, Path = originalImagePath }, options.Item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } if (options.HasDefaultOptions(originalImagePath)) { // Just spit out the original file if all the options are default return(new Tuple <string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath))); } ImageSize?originalImageSize; try { originalImageSize = GetImageSize(originalImagePath, dateModified, true); if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value)) { // Just spit out the original file if all the options are default return(new Tuple <string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath))); } } catch { originalImageSize = null; } var newSize = GetNewImageSize(options, originalImageSize); var quality = options.Quality; var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer); var semaphore = GetLock(cacheFilePath); await semaphore.WaitAsync().ConfigureAwait(false); var imageProcessingLockTaken = false; try { CheckDisposed(); if (!_fileSystem.FileExists(cacheFilePath)) { var newWidth = Convert.ToInt32(newSize.Width); var newHeight = Convert.ToInt32(newSize.Height); _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false); imageProcessingLockTaken = true; _imageEncoder.EncodeImage(originalImagePath, cacheFilePath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat); } return(new Tuple <string, string>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath))); } catch (Exception ex) { // If it fails for whatever reason, return the original image _logger.ErrorException("Error encoding image", ex); // Just spit out the original file if all the options are default return(new Tuple <string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath))); } finally { if (imageProcessingLockTaken) { _imageProcessingSemaphore.Release(); } semaphore.Release(); } }
/// <inheritdoc /> public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation?orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat) { throw new NotImplementedException(); }
public async Task <Tuple <string, string, DateTime> > ProcessImage(ImageProcessingOptions options) { if (options == null) { throw new ArgumentNullException("options"); } var originalImage = options.Image; IHasMetadata item = options.Item; if (!originalImage.IsLocalFile) { if (item == null) { item = _libraryManager().GetItemById(options.ItemId); } originalImage = await _libraryManager().ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false); } var originalImagePath = originalImage.Path; var dateModified = originalImage.DateModified; if (!_imageEncoder.SupportsImageEncoding) { return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = supportedImageInfo.Item1; dateModified = supportedImageInfo.Item2; var requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath) ?? string.Empty); if (options.Enhancers.Count > 0) { if (item == null) { item = _libraryManager().GetItemById(options.ItemId); } var tuple = await GetEnhancedImage(new ItemImageInfo { DateModified = dateModified, Type = originalImage.Type, Path = originalImagePath }, requiresTransparency, item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; requiresTransparency = tuple.Item3; } var photo = item as Photo; var autoOrient = false; ImageOrientation?orientation = null; if (photo != null && photo.Orientation.HasValue && photo.Orientation.Value != ImageOrientation.TopLeft) { autoOrient = true; orientation = photo.Orientation; } if (options.HasDefaultOptions(originalImagePath) && (!autoOrient || !options.RequiresAutoOrientation)) { // Just spit out the original file if all the options are default return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } //ImageSize? originalImageSize = GetSavedImageSize(originalImagePath, dateModified); //if (originalImageSize.HasValue && options.HasDefaultOptions(originalImagePath, originalImageSize.Value) && !autoOrient) //{ // // Just spit out the original file if all the options are default // _logger.Info("Returning original image {0}", originalImagePath); // return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); //} var newSize = ImageHelper.GetNewImageSize(options, null); var quality = options.Quality; var outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); try { CheckDisposed(); if (!_fileSystem.FileExists(cacheFilePath)) { var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath)); if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath)) { options.CropWhiteSpace = false; } var resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, tmpPath, autoOrient, orientation, quality, options, outputFormat); if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); CopyFile(tmpPath, cacheFilePath); return(new Tuple <string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath))); } return(new Tuple <string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath))); } catch (ArgumentOutOfRangeException ex) { // Decoder failed to decode it #if DEBUG _logger.ErrorException("Error encoding image", ex); #endif // Just spit out the original file if all the options are default return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } catch (Exception ex) { // If it fails for whatever reason, return the original image _logger.ErrorException("Error encoding image", ex); // Just spit out the original file if all the options are default return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } }
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { if (string.IsNullOrWhiteSpace(inputPath)) { throw new ArgumentNullException("inputPath"); } if (string.IsNullOrWhiteSpace(inputPath)) { throw new ArgumentNullException("outputPath"); } var skiaOutputFormat = GetImageFormat(selectedOutputFormat); var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor); var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer); var blur = options.Blur ?? 0; var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace)) { if (bitmap == null) { throw new Exception(string.Format("Skia unable to read image {0}", inputPath)); } //_logger.Info("Color type {0}", bitmap.Info.ColorType); var originalImageSize = new ImageSize(bitmap.Width, bitmap.Height); ImageHelper.SaveImageSize(inputPath, dateModified, originalImageSize); if (!options.CropWhiteSpace && options.HasDefaultOptions(inputPath, originalImageSize)) { // Just spit out the original file if all the options are default return(inputPath); } var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize); var width = Convert.ToInt32(Math.Round(newImageSize.Width)); var height = Convert.ToInt32(Math.Round(newImageSize.Height)); using (var resizedBitmap = new SKBitmap(width, height))//, bitmap.ColorType, bitmap.AlphaType)) { // scale image var resizeMethod = SKBitmapResizeMethod.Lanczos3; bitmap.Resize(resizedBitmap, resizeMethod); // If all we're doing is resizing then we can stop now if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) { using (var outputStream = new SKFileWStream(outputPath)) { resizedBitmap.Encode(outputStream, skiaOutputFormat, quality); return(outputPath); } } // create bitmap to use for canvas drawing using (var saveBitmap = new SKBitmap(width, height))//, bitmap.ColorType, bitmap.AlphaType)) { // create canvas used to draw into bitmap using (var canvas = new SKCanvas(saveBitmap)) { // set background color if present if (hasBackgroundColor) { canvas.Clear(SKColor.Parse(options.BackgroundColor)); } // Add blur if option is present if (blur > 0) { using (var paint = new SKPaint()) { // create image from resized bitmap to apply blur using (var filter = SKImageFilter.CreateBlur(blur, blur)) { paint.ImageFilter = filter; canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint); } } } else { // draw resized bitmap onto canvas canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height)); } // If foreground layer present then draw if (hasForegroundColor) { Double opacity; if (!Double.TryParse(options.ForegroundLayer, out opacity)) { opacity = .4; } canvas.DrawColor(new SKColor(0, 0, 0, (Byte)((1 - opacity) * 0xFF)), SKBlendMode.SrcOver); } if (hasIndicator) { DrawIndicator(canvas, width, height, options); } using (var outputStream = new SKFileWStream(outputPath)) { saveBitmap.Encode(outputStream, skiaOutputFormat, quality); } } } } } return(outputPath); }
/// <summary> /// Draws the indicator. /// </summary> /// <param name="graphics">The graphics.</param> /// <param name="imageWidth">Width of the image.</param> /// <param name="imageHeight">Height of the image.</param> /// <param name="options">The options.</param> private void DrawIndicator(Graphics graphics, int imageWidth, int imageHeight, ImageProcessingOptions options) { if (!options.AddPlayedIndicator && !options.PercentPlayed.HasValue) { return; } try { var percentOffset = 0; if (options.AddPlayedIndicator) { var currentImageSize = new Size(imageWidth, imageHeight); new WatchedIndicatorDrawer().Process(graphics, currentImageSize); percentOffset = 0 - WatchedIndicatorDrawer.IndicatorWidth; } if (options.PercentPlayed.HasValue) { var currentImageSize = new Size(imageWidth, imageHeight); new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed.Value, percentOffset); } } catch (Exception ex) { _logger.ErrorException("Error drawing indicator overlay", ex); } }
public void EncodeImage(string inputPath, string outputPath, int width, int height, int quality, ImageProcessingOptions options) { throw new NotImplementedException(); }
public async Task <string> ProcessImage(ImageProcessingOptions options) { if (options == null) { throw new ArgumentNullException("options"); } var originalImagePath = options.Image.Path; if (options.HasDefaultOptions(originalImagePath) && options.Enhancers.Count == 0 && !options.CropWhiteSpace) { // Just spit out the original file if all the options are default return(originalImagePath); } var dateModified = options.Image.DateModified; if (options.CropWhiteSpace) { var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } if (options.Enhancers.Count > 0) { var tuple = await GetEnhancedImage(options.Image, options.Item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } var originalImageSize = GetImageSize(originalImagePath, dateModified); // Determine the output size based on incoming parameters var newSize = DrawingUtils.Resize(originalImageSize, options.Width, options.Height, options.MaxWidth, options.MaxHeight); if (options.HasDefaultOptionsWithoutSize(originalImagePath) && newSize.Equals(originalImageSize) && options.Enhancers.Count == 0) { // Just spit out the original file if the new size equals the old return(originalImagePath); } var quality = options.Quality ?? 90; var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, options.OutputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor); var semaphore = GetLock(cacheFilePath); await semaphore.WaitAsync().ConfigureAwait(false); // Check again in case of lock contention try { if (File.Exists(cacheFilePath)) { semaphore.Release(); return(cacheFilePath); } } catch { semaphore.Release(); throw; } try { var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed > 0; using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, 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); var selectedOutputFormat = options.OutputFormat; _logger.Debug("Processing image to {0}", selectedOutputFormat); // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here // Also, Webp only supports Format32bppArgb and Format32bppRgb var pixelFormat = selectedOutputFormat == Model.Drawing.ImageFormat.Webp ? PixelFormat.Format32bppArgb : PixelFormat.Format32bppPArgb; using (var thumbnail = new Bitmap(newWidth, newHeight, pixelFormat)) { // Mono throw an exeception if assign 0 to SetResolution if (originalImage.HorizontalResolution > 0 && originalImage.VerticalResolution > 0) { // 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 = !hasPostProcessing ? CompositingMode.SourceCopy : CompositingMode.SourceOver; SetBackgroundColor(thumbnailGraph, options); thumbnailGraph.DrawImage(originalImage, 0, 0, newWidth, newHeight); DrawIndicator(thumbnailGraph, newWidth, newHeight, options); var outputFormat = GetOutputFormat(originalImage, selectedOutputFormat); Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); // Save to the cache location using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, false)) { if (selectedOutputFormat == Model.Drawing.ImageFormat.Webp) { SaveToWebP(thumbnail, cacheFileStream, quality); } else { // Save to the memory stream thumbnail.Save(outputFormat, cacheFileStream, quality); } } return(cacheFilePath); } } } } } } finally { semaphore.Release(); } }
public async Task <Tuple <string, string, DateTime> > ProcessImage(ImageProcessingOptions options) { if (options == null) { throw new ArgumentNullException("options"); } var originalImage = options.Image; IHasImages item = options.Item; if (!originalImage.IsLocalFile) { if (item == null) { item = _libraryManager().GetItemById(options.ItemId); } originalImage = await _libraryManager().ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false); } var originalImagePath = originalImage.Path; var dateModified = originalImage.DateModified; if (!_imageEncoder.SupportsImageEncoding) { return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } if (options.Enhancers.Count > 0) { if (item == null) { item = _libraryManager().GetItemById(options.ItemId); } var tuple = await GetEnhancedImage(new ItemImageInfo { DateModified = dateModified, Type = originalImage.Type, Path = originalImagePath }, item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } if (options.HasDefaultOptions(originalImagePath)) { // Just spit out the original file if all the options are default return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } ImageSize?originalImageSize = GetSavedImageSize(originalImagePath, dateModified); if (originalImageSize.HasValue && options.HasDefaultOptions(originalImagePath, originalImageSize.Value)) { // Just spit out the original file if all the options are default _logger.Info("Returning original image {0}", originalImagePath); return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } var newSize = ImageHelper.GetNewImageSize(options, originalImageSize); var quality = options.Quality; var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); try { CheckDisposed(); if (!_fileSystem.FileExists(cacheFilePath)) { _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath)); if (item == null && string.Equals(options.ItemType, typeof(Photo).Name, StringComparison.OrdinalIgnoreCase)) { item = _libraryManager().GetItemById(options.ItemId); } var resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, tmpPath, AutoOrient(item), quality, options, outputFormat); if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } CopyFile(tmpPath, cacheFilePath); return(new Tuple <string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath))); } return(new Tuple <string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath))); } catch (Exception ex) { // If it fails for whatever reason, return the original image _logger.ErrorException("Error encoding image", ex); // Just spit out the original file if all the options are default return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } }
public void EncodeImage(string inputPath, string outputPath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { throw new NotImplementedException(); }
public void EncodeImage(string inputPath, ImageSize?originalImageSize, string outputPath, bool autoOrient, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { using (var originalImage = GetImage(inputPath, options.CropWhiteSpace)) { if (options.CropWhiteSpace || !originalImageSize.HasValue) { originalImageSize = new ImageSize(originalImage.Width, originalImage.Height); } var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize); var newWidth = Convert.ToInt32(Math.Round(newImageSize.Width)); var newHeight = Convert.ToInt32(Math.Round(newImageSize.Height)); // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here // Also, Webp only supports Format32bppArgb and Format32bppRgb var pixelFormat = selectedOutputFormat == ImageFormat.Webp ? PixelFormat.Format32bppArgb : PixelFormat.Format32bppPArgb; using (var thumbnail = new Bitmap(newWidth, newHeight, pixelFormat)) { // Mono throw an exeception if assign 0 to SetResolution if (originalImage.HorizontalResolution > 0 && originalImage.VerticalResolution > 0) { // 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; // SourceCopy causes the image to be blank in OSX //thumbnailGraph.CompositingMode = !hasPostProcessing ? // CompositingMode.SourceCopy : // CompositingMode.SourceOver; SetBackgroundColor(thumbnailGraph, options); thumbnailGraph.DrawImage(originalImage, 0, 0, newWidth, newHeight); DrawIndicator(thumbnailGraph, newWidth, newHeight, options); var outputFormat = GetOutputFormat(originalImage, selectedOutputFormat); // Save to the cache location using (var cacheFileStream = _fileSystem.GetFileStream(outputPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, false)) { // Save to the memory stream thumbnail.Save(outputFormat, cacheFileStream, quality); } } } } }
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation?orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { // Even if the caller specified 100, don't use it because it takes forever quality = Math.Min(quality, 99); if (string.IsNullOrWhiteSpace(options.BackgroundColor) || !HasTransparency(inputPath)) { using (var originalImage = new MagickWand(inputPath)) { if (options.CropWhiteSpace) { originalImage.CurrentImage.TrimImage(10); } var originalImageSize = new ImageSize(originalImage.CurrentImage.Width, originalImage.CurrentImage.Height); if (!options.CropWhiteSpace && options.HasDefaultOptions(inputPath, originalImageSize)) { // Just spit out the original file if all the options are default return(inputPath); } var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize); var width = Convert.ToInt32(Math.Round(newImageSize.Width)); var height = Convert.ToInt32(Math.Round(newImageSize.Height)); ScaleImage(originalImage, width, height, options.Blur ?? 0); if (autoOrient) { AutoOrientImage(originalImage); } AddForegroundLayer(originalImage, options); DrawIndicator(originalImage, width, height, options); originalImage.CurrentImage.CompressionQuality = quality; originalImage.CurrentImage.StripImage(); originalImage.SaveImage(outputPath); } } else { using (var originalImage = new MagickWand(inputPath)) { var originalImageSize = new ImageSize(originalImage.CurrentImage.Width, originalImage.CurrentImage.Height); var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize); var width = Convert.ToInt32(Math.Round(newImageSize.Width)); var height = Convert.ToInt32(Math.Round(newImageSize.Height)); using (var wand = new MagickWand(width, height, options.BackgroundColor)) { ScaleImage(originalImage, width, height, options.Blur ?? 0); if (autoOrient) { AutoOrientImage(originalImage); } wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0); AddForegroundLayer(wand, options); DrawIndicator(wand, width, height, options); wand.CurrentImage.CompressionQuality = quality; wand.CurrentImage.StripImage(); wand.SaveImage(outputPath); } } } return(outputPath); }
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) { if (options == null) { throw new ArgumentNullException("options"); } if (toStream == null) { throw new ArgumentNullException("toStream"); } var originalImagePath = options.OriginalImagePath; if (options.HasDefaultOptions() && options.Enhancers.Count == 0 && !options.CropWhiteSpace) { // Just spit out the original file if all the options are default using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { await fileStream.CopyToAsync(toStream).ConfigureAwait(false); return; } } var dateModified = options.OriginalImageDateModified; if (options.CropWhiteSpace) { var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } if (options.Enhancers.Count > 0) { var tuple = await GetEnhancedImage(originalImagePath, dateModified, options.Item, options.ImageType, options.ImageIndex, options.Enhancers).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } var originalImageSize = GetImageSize(originalImagePath, dateModified); // Determine the output size based on incoming parameters var newSize = DrawingUtils.Resize(originalImageSize, options.Width, options.Height, options.MaxWidth, options.MaxHeight); if (options.HasDefaultOptionsWithoutSize() && newSize.Equals(originalImageSize) && options.Enhancers.Count == 0) { // Just spit out the original file if the new size equals the old using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { await fileStream.CopyToAsync(toStream).ConfigureAwait(false); return; } } var quality = options.Quality ?? 90; var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, options.OutputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor); try { using (var fileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { await fileStream.CopyToAsync(toStream).ConfigureAwait(false); return; } } catch (IOException) { // Cache file doesn't exist or is currently being written to } var semaphore = GetLock(cacheFilePath); await semaphore.WaitAsync().ConfigureAwait(false); // Check again in case of lock contention try { using (var fileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { await fileStream.CopyToAsync(toStream).ConfigureAwait(false); semaphore.Release(); return; } } catch (IOException) { // Cache file doesn't exist or is currently being written to } catch { semaphore.Release(); throw; } try { var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed.HasValue; //if (!hasPostProcessing) //{ // using (var outputStream = await _mediaEncoder.EncodeImage(new ImageEncodingOptions // { // InputPath = originalImagePath, // MaxHeight = options.MaxHeight, // MaxWidth = options.MaxWidth, // Height = options.Height, // Width = options.Width, // Quality = options.Quality, // Format = options.OutputFormat == ImageOutputFormat.Original ? Path.GetExtension(originalImagePath).TrimStart('.') : options.OutputFormat.ToString().ToLower() // }, CancellationToken.None).ConfigureAwait(false)) // { // using (var outputMemoryStream = new MemoryStream()) // { // // Save to the memory stream // await outputStream.CopyToAsync(outputMemoryStream).ConfigureAwait(false); // var bytes = outputMemoryStream.ToArray(); // await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); // // kick off a task to cache the result // await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false); // } // return; // } //} using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, 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 = new Bitmap(newWidth, newHeight, PixelFormat.Format32bppPArgb)) { // Mono throw an exeception if assign 0 to SetResolution if (originalImage.HorizontalResolution > 0 && originalImage.VerticalResolution > 0) { // 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 = !hasPostProcessing ? CompositingMode.SourceCopy : CompositingMode.SourceOver; SetBackgroundColor(thumbnailGraph, options); thumbnailGraph.DrawImage(originalImage, 0, 0, newWidth, newHeight); DrawIndicator(thumbnailGraph, newWidth, newHeight, options); var outputFormat = GetOutputFormat(originalImage, options.OutputFormat); using (var outputMemoryStream = new MemoryStream()) { // Save to the memory stream thumbnail.Save(outputFormat, outputMemoryStream, quality); var bytes = outputMemoryStream.ToArray(); await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); // kick off a task to cache the result await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false); } } } } } } } finally { semaphore.Release(); } }
public void EncodeImage(string inputPath, string outputPath, int width, int height, int quality, ImageProcessingOptions options) { if (string.IsNullOrWhiteSpace(options.BackgroundColor)) { using (var originalImage = new MagickWand(inputPath)) { originalImage.CurrentImage.ResizeImage(width, height); DrawIndicator(originalImage, width, height, options); originalImage.CurrentImage.CompressionQuality = quality; originalImage.SaveImage(outputPath); } } else { using (var wand = new MagickWand(width, height, options.BackgroundColor)) { using (var originalImage = new MagickWand(inputPath)) { originalImage.CurrentImage.ResizeImage(width, height); wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0); DrawIndicator(wand, width, height, options); wand.CurrentImage.CompressionQuality = quality; wand.SaveImage(outputPath); } } } }
public async Task <UploadResult> ProcessFile( IFormFile formFile, ImageProcessingOptions options, bool?resizeImages, int?maxWidth, int?maxHeight, string requestedVirtualPath = "", string newFileName = "", bool allowRootPath = true ) { await EnsureProjectSettings().ConfigureAwait(false); string currentFsPath = rootPath.RootFileSystemPath; string currentVirtualPath = rootPath.RootVirtualPath; string[] virtualSegments = options.ImageDefaultVirtualSubPath.Split('/'); bool doResize = resizeImages.HasValue ? resizeImages.Value : options.AutoResize; if ((!string.IsNullOrEmpty(requestedVirtualPath)) && (requestedVirtualPath.StartsWith(rootPath.RootVirtualPath))) { var virtualSubPath = requestedVirtualPath.Substring(rootPath.RootVirtualPath.Length); var segments = virtualSubPath.Split('/'); if (segments.Length > 0) { var requestedFsPath = Path.Combine(rootPath.RootFileSystemPath, Path.Combine(segments)); if (!Directory.Exists(requestedFsPath)) { log.LogError("directory not found for currentPath " + requestedFsPath); } else { currentVirtualPath = requestedVirtualPath; virtualSegments = segments; currentFsPath = Path.Combine(currentFsPath, Path.Combine(virtualSegments)); } } } else { // only ensure the folders if no currentDir provided, // if it is provided it must be an existing path // options.ImageDefaultVirtualSubPath might not exist on first upload so need to ensure it if (!allowRootPath) { currentVirtualPath = currentVirtualPath + options.ImageDefaultVirtualSubPath; currentFsPath = Path.Combine(currentFsPath, Path.Combine(virtualSegments)); EnsureSubFolders(rootPath.RootFileSystemPath, virtualSegments); } } string newName; if (!string.IsNullOrEmpty(newFileName)) { newName = nameRules.GetCleanFileName(newFileName); } else { newName = nameRules.GetCleanFileName(Path.GetFileName(formFile.FileName)); } var newUrl = currentVirtualPath + "/" + newName; var fsPath = Path.Combine(currentFsPath, newName); var ext = Path.GetExtension(newName); var webSizeName = Path.GetFileNameWithoutExtension(newName) + "-ws" + ext; var webFsPath = Path.Combine(currentFsPath, webSizeName); string webUrl = string.Empty; var didResize = false; try { using (var stream = new FileStream(fsPath, FileMode.Create)) { await formFile.CopyToAsync(stream); } if ((doResize) && IsWebImageFile(ext)) { var mimeType = GetMimeType(ext); webUrl = currentVirtualPath + "/" + webSizeName; int resizeWidth = GetMaxWidth(maxWidth, options); int resizeHeight = GetMaxWidth(maxHeight, options); didResize = imageResizer.ResizeImage( fsPath, currentFsPath, webSizeName, mimeType, resizeWidth, resizeHeight, options.AllowEnlargement ); } return(new UploadResult { OriginalUrl = newUrl, ResizedUrl = didResize? webUrl : string.Empty, Name = newName, Length = formFile.Length, Type = formFile.ContentType }); } catch (Exception ex) { log.LogError(MediaLoggingEvents.FILE_PROCCESSING, ex, ex.StackTrace); return(new UploadResult { ErrorMessage = sr["There was an error logged during file processing"] }); } }
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation?orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { if (string.IsNullOrWhiteSpace(inputPath)) { throw new ArgumentNullException(nameof(inputPath)); } if (string.IsNullOrWhiteSpace(inputPath)) { throw new ArgumentNullException(nameof(outputPath)); } var skiaOutputFormat = GetImageFormat(selectedOutputFormat); var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor); var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer); var blur = options.Blur ?? 0; var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation)) { if (bitmap == null) { throw new ArgumentOutOfRangeException(string.Format("Skia unable to read image {0}", inputPath)); } var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height); if (!options.CropWhiteSpace && options.HasDefaultOptions(inputPath, originalImageSize) && !autoOrient) { // Just spit out the original file if all the options are default return(inputPath); } var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize); var width = newImageSize.Width; var height = newImageSize.Height; using (var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType)) { // scale image bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); // If all we're doing is resizing then we can stop now if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); using (var outputStream = new SKFileWStream(outputPath)) using (var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels())) { pixmap.Encode(outputStream, skiaOutputFormat, quality); return(outputPath); } } // create bitmap to use for canvas drawing used to draw into bitmap using (var saveBitmap = new SKBitmap(width, height)) //, bitmap.ColorType, bitmap.AlphaType)) using (var canvas = new SKCanvas(saveBitmap)) { // set background color if present if (hasBackgroundColor) { canvas.Clear(SKColor.Parse(options.BackgroundColor)); } // Add blur if option is present if (blur > 0) { // create image from resized bitmap to apply blur using (var paint = new SKPaint()) using (var filter = SKImageFilter.CreateBlur(blur, blur)) { paint.ImageFilter = filter; canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint); } } else { // draw resized bitmap onto canvas canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height)); } // If foreground layer present then draw if (hasForegroundColor) { if (!double.TryParse(options.ForegroundLayer, out double opacity)) { opacity = .4; } canvas.DrawColor(new SKColor(0, 0, 0, (byte)((1 - opacity) * 0xFF)), SKBlendMode.SrcOver); } if (hasIndicator) { DrawIndicator(canvas, width, height, options); } Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); using (var outputStream = new SKFileWStream(outputPath)) { using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels())) { pixmap.Encode(outputStream, skiaOutputFormat, quality); } } } } } return(outputPath); }
public void EncodeImage(string inputPath, string outputPath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { // Even if the caller specified 100, don't use it because it takes forever quality = Math.Min(quality, 99); if (string.IsNullOrWhiteSpace(options.BackgroundColor) || !HasTransparency(inputPath)) { using (var originalImage = new MagickWand(inputPath)) { ScaleImage(originalImage, width, height, options.Blur ?? 0); if (autoOrient) { AutoOrientImage(originalImage); } AddForegroundLayer(originalImage, options); DrawIndicator(originalImage, width, height, options); originalImage.CurrentImage.CompressionQuality = quality; originalImage.CurrentImage.StripImage(); originalImage.SaveImage(outputPath); } } else { using (var wand = new MagickWand(width, height, options.BackgroundColor)) { using (var originalImage = new MagickWand(inputPath)) { ScaleImage(originalImage, width, height, options.Blur ?? 0); if (autoOrient) { AutoOrientImage(originalImage); } wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0); AddForegroundLayer(wand, options); DrawIndicator(wand, width, height, options); wand.CurrentImage.CompressionQuality = quality; wand.CurrentImage.StripImage(); wand.SaveImage(outputPath); } } } }
/// <inheritdoc /> public async Task <(string path, string?mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options) { ItemImageInfo originalImage = options.Image; BaseItem item = options.Item; string originalImagePath = originalImage.Path; DateTime dateModified = originalImage.DateModified; ImageDimensions?originalImageSize = null; if (originalImage.Width > 0 && originalImage.Height > 0) { originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height); } if (!_imageEncoder.SupportsImageEncoding) { return(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = supportedImageInfo.path; if (!File.Exists(originalImagePath)) { return(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } dateModified = supportedImageInfo.dateModified; bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); bool autoOrient = false; ImageOrientation?orientation = null; if (item is Photo photo) { if (photo.Orientation.HasValue) { if (photo.Orientation.Value != ImageOrientation.TopLeft) { autoOrient = true; orientation = photo.Orientation; } } else { // Orientation unknown, so do it autoOrient = true; orientation = photo.Orientation; } } if (options.HasDefaultOptions(originalImagePath, originalImageSize) && (!autoOrient || !options.RequiresAutoOrientation)) { // Just spit out the original file if all the options are default return(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } int quality = options.Quality; ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); string cacheFilePath = GetCacheFilePath( originalImagePath, options.Width, options.Height, options.MaxWidth, options.MaxHeight, options.FillWidth, options.FillHeight, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); try { if (!File.Exists(cacheFilePath)) { string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { return(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } } return(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); } catch (Exception ex) { // If it fails for whatever reason, return the original image _logger.LogError(ex, "Error encoding image"); return(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } }
public async Task <string> ProcessImage(ImageProcessingOptions options) { if (options == null) { throw new ArgumentNullException("options"); } var originalImagePath = options.Image.Path; if (options.HasDefaultOptions(originalImagePath) && options.Enhancers.Count == 0 && !options.CropWhiteSpace) { // Just spit out the original file if all the options are default return(originalImagePath); } var dateModified = options.Image.DateModified; var length = options.Image.Length; if (options.CropWhiteSpace) { var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified, length).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; length = tuple.Item3; } if (options.Enhancers.Count > 0) { var tuple = await GetEnhancedImage(new ItemImageInfo { Length = length, DateModified = dateModified, Type = options.Image.Type, Path = originalImagePath }, options.Item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; length = tuple.Item3; } var originalImageSize = GetImageSize(originalImagePath, dateModified); // Determine the output size based on incoming parameters var newSize = DrawingUtils.Resize(originalImageSize, options.Width, options.Height, options.MaxWidth, options.MaxHeight); if (options.HasDefaultOptionsWithoutSize(originalImagePath) && newSize.Equals(originalImageSize) && options.Enhancers.Count == 0) { // Just spit out the original file if the new size equals the old return(originalImagePath); } var quality = options.Quality ?? 90; var outputFormat = GetOutputFormat(options.OutputFormat); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, length, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor); var semaphore = GetLock(cacheFilePath); await semaphore.WaitAsync().ConfigureAwait(false); try { CheckDisposed(); if (!File.Exists(cacheFilePath)) { var newWidth = Convert.ToInt32(newSize.Width); var newHeight = Convert.ToInt32(newSize.Height); Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); _imageEncoder.EncodeImage(originalImagePath, cacheFilePath, newWidth, newHeight, quality, options); } } finally { semaphore.Release(); } return(cacheFilePath); }
public void EncodeImage(string inputPath, string cacheFilePath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed > 0; using (var originalImage = Image.FromFile(inputPath)) { var newWidth = Convert.ToInt32(width); var newHeight = Convert.ToInt32(height); // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here // Also, Webp only supports Format32bppArgb and Format32bppRgb var pixelFormat = selectedOutputFormat == ImageFormat.Webp ? PixelFormat.Format32bppArgb : PixelFormat.Format32bppPArgb; using (var thumbnail = new Bitmap(newWidth, newHeight, pixelFormat)) { // Mono throw an exeception if assign 0 to SetResolution if (originalImage.HorizontalResolution > 0 && originalImage.VerticalResolution > 0) { // 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; // SourceCopy causes the image to be blank in OSX //thumbnailGraph.CompositingMode = !hasPostProcessing ? // CompositingMode.SourceCopy : // CompositingMode.SourceOver; SetBackgroundColor(thumbnailGraph, options); thumbnailGraph.DrawImage(originalImage, 0, 0, newWidth, newHeight); DrawIndicator(thumbnailGraph, newWidth, newHeight, options); var outputFormat = GetOutputFormat(originalImage, selectedOutputFormat); _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); // Save to the cache location using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, false)) { // Save to the memory stream thumbnail.Save(outputFormat, cacheFileStream, quality); } } } } }
public async Task <string> ProcessImage(ImageProcessingOptions options) { if (options == null) { throw new ArgumentNullException("options"); } var originalImagePath = options.Image.Path; if (options.HasDefaultOptions(originalImagePath) && options.Enhancers.Count == 0 && !options.CropWhiteSpace) { // Just spit out the original file if all the options are default return(originalImagePath); } var dateModified = options.Image.DateModified; if (options.CropWhiteSpace) { var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } if (options.Enhancers.Count > 0) { var tuple = await GetEnhancedImage(options.Image, options.Item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } var originalImageSize = GetImageSize(originalImagePath, dateModified); // Determine the output size based on incoming parameters var newSize = DrawingUtils.Resize(originalImageSize, options.Width, options.Height, options.MaxWidth, options.MaxHeight); if (options.HasDefaultOptionsWithoutSize(originalImagePath) && newSize.Equals(originalImageSize) && options.Enhancers.Count == 0) { // Just spit out the original file if the new size equals the old return(originalImagePath); } var quality = options.Quality ?? 90; var outputFormat = GetOutputFormat(options.OutputFormat); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor); var semaphore = GetLock(cacheFilePath); await semaphore.WaitAsync().ConfigureAwait(false); // Check again in case of lock contention try { if (File.Exists(cacheFilePath)) { semaphore.Release(); return(cacheFilePath); } } catch { semaphore.Release(); throw; } try { CheckDisposed(); var newWidth = Convert.ToInt32(newSize.Width); var newHeight = Convert.ToInt32(newSize.Height); Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); if (string.IsNullOrWhiteSpace(options.BackgroundColor)) { using (var originalImage = new MagickWand(originalImagePath)) { originalImage.CurrentImage.ResizeImage(newWidth, newHeight); DrawIndicator(originalImage, newWidth, newHeight, options); originalImage.CurrentImage.CompressionQuality = quality; originalImage.SaveImage(cacheFilePath); return(cacheFilePath); } } else { using (var wand = new MagickWand(newWidth, newHeight, options.BackgroundColor)) { using (var originalImage = new MagickWand(originalImagePath)) { originalImage.CurrentImage.ResizeImage(newWidth, newHeight); wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0); DrawIndicator(wand, newWidth, newHeight, options); wand.CurrentImage.CompressionQuality = quality; wand.SaveImage(cacheFilePath); return(cacheFilePath); } } } } finally { semaphore.Release(); } }
public async Task <Tuple <string, string, DateTime> > ProcessImage(ImageProcessingOptions options) { if (options == null) { throw new ArgumentNullException("options"); } var originalImage = options.Image; var item = options.Item; if (!originalImage.IsLocalFile) { if (item == null) { item = _libraryManager().GetItemById(options.ItemId); } originalImage = await _libraryManager().ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false); } var originalImagePath = originalImage.Path; var dateModified = originalImage.DateModified; var originalImageSize = originalImage.Width > 0 && originalImage.Height > 0 ? new ImageSize(originalImage.Width, originalImage.Height) : (ImageSize?)null; if (!_imageEncoder.SupportsImageEncoding) { return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = supportedImageInfo.Item1; dateModified = supportedImageInfo.Item2; var requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath) ?? string.Empty); if (options.Enhancers.Length > 0) { if (item == null) { item = _libraryManager().GetItemById(options.ItemId); } var tuple = await GetEnhancedImage(new ItemImageInfo { DateModified = dateModified, Type = originalImage.Type, Path = originalImagePath }, requiresTransparency, item, options.ImageIndex, options.Enhancers, CancellationToken.None).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; requiresTransparency = tuple.Item3; // TODO: Get this info originalImageSize = null; } var photo = item as Photo; var autoOrient = false; ImageOrientation?orientation = null; if (photo != null) { if (photo.Orientation.HasValue) { if (photo.Orientation.Value != ImageOrientation.TopLeft) { autoOrient = true; orientation = photo.Orientation; } } else { // Orientation unknown, so do it autoOrient = true; orientation = photo.Orientation; } } if (options.HasDefaultOptions(originalImagePath, originalImageSize) && (!autoOrient || !options.RequiresAutoOrientation)) { // Just spit out the original file if all the options are default return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } //ImageSize? originalImageSize = GetSavedImageSize(originalImagePath, dateModified); //if (originalImageSize.HasValue && options.HasDefaultOptions(originalImagePath, originalImageSize.Value) && !autoOrient) //{ // // Just spit out the original file if all the options are default // _logger.LogInformation("Returning original image {0}", originalImagePath); // return new ValueTuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); //} var newSize = ImageHelper.GetNewImageSize(options, null); var quality = options.Quality; var outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); CheckDisposed(); var lockInfo = GetLock(cacheFilePath); await lockInfo.Lock.WaitAsync().ConfigureAwait(false); try { if (!_fileSystem.FileExists(cacheFilePath)) { if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath)) { options.CropWhiteSpace = false; } var resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } return(new Tuple <string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath))); } return(new Tuple <string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath))); } catch (ArgumentOutOfRangeException ex) { // Decoder failed to decode it #if DEBUG _logger.LogError(ex, "Error encoding image"); #endif // Just spit out the original file if all the options are default return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } catch (Exception ex) { // If it fails for whatever reason, return the original image _logger.LogError(ex, "Error encoding image"); // Just spit out the original file if all the options are default return(new Tuple <string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified)); } finally { ReleaseLock(cacheFilePath, lockInfo); } }
public async Task <string> ProcessImage(ImageProcessingOptions options) { if (options == null) { throw new ArgumentNullException("options"); } var originalImage = options.Image; if (!originalImage.IsLocalFile) { originalImage = await _libraryManager().ConvertImageToLocal(options.Item, originalImage, options.ImageIndex).ConfigureAwait(false); } var originalImagePath = originalImage.Path; if (!_imageEncoder.SupportsImageEncoding) { return(originalImagePath); } if (options.HasDefaultOptions(originalImagePath) && options.Enhancers.Count == 0 && !options.CropWhiteSpace) { // Just spit out the original file if all the options are default return(originalImagePath); } var dateModified = originalImage.DateModified; if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding) { var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } if (options.Enhancers.Count > 0) { var tuple = await GetEnhancedImage(new ItemImageInfo { DateModified = dateModified, Type = originalImage.Type, Path = originalImagePath }, options.Item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; } var newSizeInfo = GetNewImageSize(originalImagePath, dateModified, options); var newSize = newSizeInfo.Item1; var isSizeChanged = newSizeInfo.Item2; if (options.HasDefaultOptionsWithoutSize(originalImagePath) && !isSizeChanged && options.Enhancers.Count == 0) { // Just spit out the original file if the new size equals the old return(originalImagePath); } var quality = options.Quality ?? 90; var outputFormat = GetOutputFormat(options.OutputFormat); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor); var semaphore = GetLock(cacheFilePath); await semaphore.WaitAsync().ConfigureAwait(false); var imageProcessingLockTaken = false; try { CheckDisposed(); if (!_fileSystem.FileExists(cacheFilePath)) { var newWidth = Convert.ToInt32(newSize.Width); var newHeight = Convert.ToInt32(newSize.Height); _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false); imageProcessingLockTaken = true; _imageEncoder.EncodeImage(originalImagePath, cacheFilePath, newWidth, newHeight, quality, options); } } finally { if (imageProcessingLockTaken) { _imageProcessingSemaphore.Release(); } semaphore.Release(); } return(cacheFilePath); }