/Main/ImageResizer/Services/ResizingService.cs

# · C# · 184 lines · 129 code · 32 blank · 23 comment · 21 complexity · 8060a699c84c58a508b3eccdc1871eb7 MD5 · raw file

  1. //------------------------------------------------------------------------------
  2. // <copyright file="ResizingService.cs" company="Brice Lambson">
  3. // Copyright (c) 2011 Brice Lambson. All rights reserved.
  4. //
  5. // The use of this software is governed by the Microsoft Public License
  6. // which is included with this distribution.
  7. // </copyright>
  8. //------------------------------------------------------------------------------
  9. namespace BriceLambson.ImageResizer.Services
  10. {
  11. using System;
  12. using System.Diagnostics.Contracts;
  13. using System.Globalization;
  14. using System.IO;
  15. using System.Windows.Media;
  16. using System.Windows.Media.Imaging;
  17. using BriceLambson.ImageResizer.Helpers;
  18. using BriceLambson.ImageResizer.Models;
  19. using Microsoft.VisualBasic.FileIO;
  20. internal class ResizingService
  21. {
  22. private const string DefaultEncoderExtension = ".jpg";
  23. private static readonly Type DefaultEncoderType = typeof(JpegBitmapEncoder);
  24. private readonly int _qualityLevel;
  25. private readonly bool _shrinkOnly;
  26. private readonly bool _ignoreRotations;
  27. private readonly ResizeSize _size;
  28. private readonly RenamingService _renamer;
  29. public ResizingService(int qualityLevel, bool shrinkOnly, bool ignoreRotations, ResizeSize size, RenamingService renamer)
  30. {
  31. Contract.Requires(qualityLevel >= 1 && qualityLevel <= 100);
  32. Contract.Requires(size != null);
  33. Contract.Requires(renamer != null);
  34. _qualityLevel = qualityLevel;
  35. _shrinkOnly = shrinkOnly;
  36. _ignoreRotations = ignoreRotations;
  37. _size = size;
  38. _renamer = renamer;
  39. }
  40. public string Resize(string sourcePath)
  41. {
  42. Contract.Requires(!String.IsNullOrWhiteSpace(sourcePath));
  43. Contract.Ensures(!String.IsNullOrWhiteSpace(Contract.Result<string>()));
  44. var encoderDefaulted = false;
  45. BitmapDecoder decoder;
  46. using (var sourceStream = File.OpenRead(sourcePath))
  47. {
  48. // NOTE: Using BitmapCacheOption.OnLoad here will read the entire file into
  49. // memory which allows us to dispose of the file stream immediately
  50. decoder = BitmapDecoder.Create(sourceStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
  51. }
  52. var encoder = BitmapEncoder.Create(decoder.CodecInfo.ContainerFormat);
  53. try
  54. {
  55. // NOTE: This will throw if the codec dose not support encoding
  56. var _ = encoder.CodecInfo;
  57. }
  58. catch (NotSupportedException)
  59. {
  60. // Fallback to JPEG encoder
  61. encoder = (BitmapEncoder)Activator.CreateInstance(DefaultEncoderType);
  62. encoderDefaulted = true;
  63. }
  64. // TODO: Copy container-level metadata if codec supports it
  65. SetEncoderSettings(encoder);
  66. string destinationPath = null;
  67. // NOTE: Only TIFF and GIF images support multiple frames
  68. foreach (var sourceFrame in decoder.Frames)
  69. {
  70. // Apply the transform
  71. var transform = GetTransform(sourceFrame);
  72. var transformedBitmap = new TransformedBitmap(sourceFrame, transform);
  73. // TODO: Optionally copy metadata
  74. // Create the destination frame
  75. var thumbnail = sourceFrame.Thumbnail;
  76. var metadata = sourceFrame.Metadata as BitmapMetadata;
  77. var colorContexts = sourceFrame.ColorContexts;
  78. var destinationFrame = BitmapFrame.Create(transformedBitmap, thumbnail, metadata, colorContexts);
  79. encoder.Frames.Add(destinationFrame);
  80. // Set the destination path using the first frame
  81. if (destinationPath == null)
  82. {
  83. if (encoderDefaulted)
  84. {
  85. sourcePath = Path.ChangeExtension(sourcePath, DefaultEncoderExtension);
  86. }
  87. destinationPath = _renamer.Rename(sourcePath);
  88. }
  89. }
  90. // Move any existing file to the Recycle Bin
  91. if (File.Exists(destinationPath))
  92. {
  93. // TODO: Is there a better way to do this without a reference to Microsoft.VisualBasic?
  94. FileSystem.DeleteFile(destinationPath, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
  95. }
  96. using (var destinationStream = File.OpenWrite(destinationPath))
  97. {
  98. // Save the final image
  99. encoder.Save(destinationStream);
  100. }
  101. return destinationPath;
  102. }
  103. private void SetEncoderSettings(BitmapEncoder encoder)
  104. {
  105. Contract.Requires(encoder != null);
  106. var jpegEncoder = encoder as JpegBitmapEncoder;
  107. if (jpegEncoder != null)
  108. {
  109. jpegEncoder.QualityLevel = _qualityLevel;
  110. }
  111. }
  112. // NOTE: This could be changed to return a TransformGroup which would allow a
  113. // combination of transforms to be performed on the image
  114. private Transform GetTransform(BitmapSource source)
  115. {
  116. Contract.Requires(source != null);
  117. var width = _size.Width;
  118. var height = _size.Height;
  119. if (_ignoreRotations)
  120. {
  121. if ((width > height) != (source.PixelWidth > source.PixelHeight))
  122. {
  123. var temp = width;
  124. width = height;
  125. height = temp;
  126. }
  127. }
  128. var scaleX = UnitHelper.ConvertToScale(width, _size.Unit, source.PixelWidth, source.DpiX);
  129. var scaleY = UnitHelper.ConvertToScale(height, _size.Unit, source.PixelHeight, source.DpiY);
  130. if (_size.Mode == Mode.Scale)
  131. {
  132. var minScale = Math.Min(scaleX, scaleY);
  133. scaleX = minScale;
  134. scaleY = minScale;
  135. }
  136. else if (_size.Mode != Mode.Stretch)
  137. {
  138. throw new NotSupportedException(String.Format(CultureInfo.InvariantCulture, "The mode '{0}' is not yet supported.", _size.Mode));
  139. }
  140. if (_shrinkOnly)
  141. {
  142. var maxScale = Math.Max(scaleX, scaleY);
  143. if (maxScale > 1.0)
  144. {
  145. scaleX = 1.0;
  146. scaleY = 1.0;
  147. }
  148. }
  149. return new ScaleTransform(scaleX, scaleY);
  150. }
  151. }
  152. }