Browse Source

New cache cleanup mechanism

Former-commit-id: f5933f3e909634ebd878cda4f3b24289012e90c6
af/merge-core
James South 12 years ago
parent
commit
6eb6d747d2
  1. 15
      src/ImageProcessor.Web/NET4/ImageProcessor.Web.csproj
  2. 34
      src/ImageProcessor.Web/NET45/Caching/CacheIndexer.cs
  3. 32
      src/ImageProcessor.Web/NET45/Caching/CleanupImage.cs
  4. 97
      src/ImageProcessor.Web/NET45/Caching/DiskCache.cs
  5. 12
      src/ImageProcessor.Web/NET45/Caching/MemCache.cs
  6. 42
      src/ImageProcessor.Web/NET45/Caching/SQLContext.cs
  7. 36
      src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
  8. 5
      src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj
  9. 4
      src/ImageProcessor.Web/NET45/Properties/AssemblyInfo.cs
  10. 4
      src/ImageProcessor/Properties/AssemblyInfo.cs
  11. 4
      src/TestWebsites/NET45/Test_Website_NET45/Test_Website_NET45.csproj
  12. 5
      src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml
  13. 1
      src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Png.cshtml

15
src/ImageProcessor.Web/NET4/ImageProcessor.Web.csproj

@ -45,6 +45,7 @@
<Reference Include="Community.CsharpSqlite.SQLiteClient">
<HintPath>..\..\packages\Csharp-Sqlite.3.7.7.1\lib\net40\Community.CsharpSqlite.SQLiteClient.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Microsoft.Bcl.Async.1.0.16\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
@ -74,15 +75,12 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\NET45\Caching\CachedImage.cs" />
<Compile Include="..\NET45\Caching\CacheManager.cs">
<Link>CacheManager.cs</Link>
</Compile>
<Compile Include="..\NET45\Caching\CleanupImage.cs">
<Link>CleanupImage.cs</Link>
<Compile Include="..\NET45\Caching\CacheIndexer.cs">
<Link>CacheIndexer.cs</Link>
</Compile>
<Compile Include="..\NET45\Caching\DiskCache.cs" />
<Compile Include="..\NET45\Caching\MemoryCache.cs">
<Link>MemoryCache.cs</Link>
<Compile Include="..\NET45\Caching\MemCache.cs">
<Link>MemCache.cs</Link>
</Compile>
<Compile Include="..\NET45\Caching\SQLContext.cs" />
<Compile Include="..\NET45\Config\ImageCacheSection.cs" />
@ -93,6 +91,9 @@
<Compile Include="..\NET45\Helpers\TaskHelpers.cs" />
<Compile Include="..\NET45\HttpModules\ImageProcessingModule.cs" />
<Compile Include="..\NET45\ImageFactoryExtensions.cs" />
<Compile Include="..\NET45\Preset.cs">
<Link>Preset.cs</Link>
</Compile>
<Compile Include="..\NET45\Properties\AssemblyInfo.cs" />
<Compile Include="..\NET45\SQLite.cs" />
<Compile Include="..\NET45\SQLiteAsync.cs" />

34
src/ImageProcessor.Web/NET45/Caching/MemoryCache.cs → src/ImageProcessor.Web/NET45/Caching/CacheIndexer.cs

@ -1,10 +1,10 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MemoryCache.cs" company="James South">
// <copyright file="CacheIndexer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
//
// Represents an in memory collection of keys and values whose operations are concurrent.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
@ -18,15 +18,15 @@ namespace ImageProcessor.Web.Caching
/// <summary>
/// Represents an in memory collection of keys and values whose operations are concurrent.
/// </summary>
internal sealed class MemoryCache
internal sealed class CacheIndexer
{
#region Fields
/// <summary>
/// A new instance Initializes a new instance of the <see cref="T:ImageProcessor.Web.Caching.MemoryCache"/> class.
/// A new instance Initializes a new instance of the <see cref="T:ImageProcessor.Web.Caching.CacheIndexer"/> class.
/// initialized lazily.
/// </summary>
private static readonly Lazy<MemoryCache> Lazy =
new Lazy<MemoryCache>(() => new MemoryCache());
private static readonly Lazy<CacheIndexer> Lazy =
new Lazy<CacheIndexer>(() => new CacheIndexer());
/// <summary>
/// The object to lock against.
@ -36,19 +36,19 @@ namespace ImageProcessor.Web.Caching
#region Constructors
/// <summary>
/// Prevents a default instance of the <see cref="T:ImageProcessor.Web.Caching.MemoryCache"/> class
/// Prevents a default instance of the <see cref="T:ImageProcessor.Web.Caching.CacheIndexer"/> class
/// from being created.
/// </summary>
private MemoryCache()
private CacheIndexer()
{
this.LoadCache();
}
#endregion
/// <summary>
/// Gets the current instance of the <see cref="T:ImageProcessor.Web.Caching.MemoryCache"/> class.
/// Gets the current instance of the <see cref="T:ImageProcessor.Web.Caching.CacheIndexer"/> class.
/// </summary>
public static MemoryCache Instance
public static CacheIndexer Instance
{
get
{
@ -64,12 +64,12 @@ namespace ImageProcessor.Web.Caching
/// The key of the value to get.
/// </param>
/// <returns>
/// The <see cref="CachedImage"/> matching the given key if the <see cref="MemoryCache"/> contains an element with
/// The <see cref="CachedImage"/> matching the given key if the <see cref="CacheIndexer"/> contains an element with
/// the specified key; otherwise, null.
/// </returns>
public async Task<CachedImage> GetValueAsync(string key)
{
CachedImage cachedImage = (CachedImage)CacheManager.GetItem(key);
CachedImage cachedImage = (CachedImage)MemCache.GetItem(key);
if (cachedImage == null)
{
@ -77,7 +77,7 @@ namespace ImageProcessor.Web.Caching
if (cachedImage != null)
{
CacheManager.AddItem(key, cachedImage);
MemCache.AddItem(key, cachedImage);
}
}
@ -91,14 +91,14 @@ namespace ImageProcessor.Web.Caching
/// The key of the item to remove.
/// </param>
/// <returns>
/// true if the <see cref="MemoryCache"/> removes an element with
/// true if the <see cref="CacheIndexer"/> removes an element with
/// the specified key; otherwise, false.
/// </returns>
public async Task<bool> RemoveAsync(string key)
{
if (await this.SaveCacheAsync(key, null, true) > 0)
{
CacheManager.RemoveItem(key);
MemCache.RemoveItem(key);
return true;
}
@ -122,7 +122,7 @@ namespace ImageProcessor.Web.Caching
// Add the CachedImage.
if (await this.SaveCacheAsync(key, cachedImage, false) > 0)
{
CacheManager.AddItem(key, cachedImage);
MemCache.AddItem(key, cachedImage);
}
return cachedImage;
@ -130,7 +130,7 @@ namespace ImageProcessor.Web.Caching
#endregion
/// <summary>
/// Saves the in memory cache to the file-system.
/// Saves the image to the file-system cache.
/// </summary>
/// <param name="key">
/// The key.

32
src/ImageProcessor.Web/NET45/Caching/CleanupImage.cs

@ -1,32 +0,0 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CleanupImage.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Describes a cached image for cleanup.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Caching
{
#region Using
using System;
#endregion
/// <summary>
/// Describes a cached image for cleanup
/// </summary>
public sealed class CleanupImage
{
/// <summary>
/// Gets or sets the value of the cached image.
/// </summary>
public string Path { get; set; }
/// <summary>
/// Gets or sets when the cached image should expire from the cache.
/// </summary>
public DateTime ExpiresUtc { get; set; }
}
}

97
src/ImageProcessor.Web/NET45/Caching/DiskCache.cs

@ -12,11 +12,11 @@ namespace ImageProcessor.Web.Caching
{
#region Using
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
@ -47,17 +47,7 @@ namespace ImageProcessor.Web.Caching
/// <see cref="http://stackoverflow.com/questions/115882/how-do-you-deal-with-lots-of-small-files"/>
/// <see cref="http://stackoverflow.com/questions/1638219/millions-of-small-graphics-files-and-how-to-overcome-slow-file-system-access-on"/>
/// </remarks>
private const int MaxFilesCount = 100;
/// <summary>
/// The regular expression to search strings for valid subfolder names.
/// We're specifically not using a shorter regex as we need to be able to iterate through
/// each match group.
/// </summary>
private static readonly Regex SubFolderRegex =
new Regex(
@"(\/([a-z]|[0-9])\/(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|0|1|2|3|4|5|6|7|8|9)\/)",
RegexOptions.Compiled);
private const int MaxFilesCount = 50;
/// <summary>
/// The absolute path to virtual cache path on the server.
@ -171,7 +161,7 @@ namespace ImageProcessor.Web.Caching
ExpiresUtc = expires
};
await MemoryCache.Instance.AddAsync(key, cachedImage);
await CacheIndexer.Instance.AddAsync(key, cachedImage);
}
/// <summary>
@ -188,7 +178,7 @@ namespace ImageProcessor.Web.Caching
if (this.isRemote)
{
cachedImage = await MemoryCache.Instance.GetValueAsync(key);
cachedImage = await CacheIndexer.Instance.GetValueAsync(key);
if (cachedImage != null)
{
@ -197,7 +187,7 @@ namespace ImageProcessor.Web.Caching
if (cachedImage.ExpiresUtc < DateTime.UtcNow.AddDays(-MaxFileCachedDuration)
|| cachedImage.MaxAge != MaxFileCachedDuration)
{
if (await MemoryCache.Instance.RemoveAsync(key))
if (await CacheIndexer.Instance.RemoveAsync(key))
{
isUpdated = true;
}
@ -212,7 +202,7 @@ namespace ImageProcessor.Web.Caching
else
{
// Test now for locally requested files.
cachedImage = await MemoryCache.Instance.GetValueAsync(key);
cachedImage = await CacheIndexer.Instance.GetValueAsync(key);
if (cachedImage != null)
{
@ -226,7 +216,7 @@ namespace ImageProcessor.Web.Caching
|| cachedImage.ExpiresUtc < DateTime.UtcNow.AddDays(-MaxFileCachedDuration)
|| cachedImage.MaxAge != MaxFileCachedDuration)
{
if (await MemoryCache.Instance.RemoveAsync(key))
if (await CacheIndexer.Instance.RemoveAsync(key))
{
isUpdated = true;
}
@ -254,7 +244,7 @@ namespace ImageProcessor.Web.Caching
string key = Path.GetFileNameWithoutExtension(this.CachedPath);
DateTime dateTime = DateTime.UtcNow;
CachedImage cachedImage = await MemoryCache.Instance.GetValueAsync(key);
CachedImage cachedImage = await CacheIndexer.Instance.GetValueAsync(key);
if (cachedImage != null)
{
@ -277,15 +267,17 @@ namespace ImageProcessor.Web.Caching
}
/// <summary>
/// Purges any files from the file-system cache in the given folders.
/// Trims a cached folder ensuring that it does not exceed the maximum file count.
/// </summary>
/// <param name="path">
/// The path to the folder.
/// </param>
/// <returns>
/// The <see cref="T:System.Threading.Tasks.Task"/>.
/// </returns>
internal async Task TrimCachedFoldersAsync()
internal async Task TrimCachedFolderAsync(string path)
{
// Create Action delegate for TrimCachedFolders.
await TaskHelpers.Run(this.TrimCachedFolders);
await TaskHelpers.Run(() => this.TrimCachedFolder(path));
}
#endregion
@ -325,50 +317,42 @@ namespace ImageProcessor.Web.Caching
}
/// <summary>
/// Purges any files from the file-system cache in the given folders.
/// Trims a cached folder ensuring that it does not exceed the maximum file count.
/// </summary>
private async void TrimCachedFolders()
/// <param name="path">
/// The path to the folder.
/// </param>
private async void TrimCachedFolder(string path)
{
// Group each cache folder and clear any expired items or any that exceed
// the maximum allowable count.
var groups = SQLContext.GetImagesForCleanup()
.GroupBy(x => SubFolderRegex.Match(x.Path).Value)
.Where(g => g.Count() > MaxFilesCount);
// ReSharper disable once AssignNullToNotNullAttribute
DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path));
IEnumerable<FileInfo> files = directoryInfo.EnumerateFiles().OrderBy(f => f.LastWriteTimeUtc);
int count = files.Count();
foreach (var group in groups)
foreach (FileInfo fileInfo in files)
{
int groupCount = group.Count();
foreach (CleanupImage image in group.OrderBy(x => x.ExpiresUtc))
try
{
// If the group count is equal to the max count minus 1 then we know we
// are counting down from a full directory not simply clearing out
// expired items.
if (groupCount <= MaxFilesCount - 1
&& image.ExpiresUtc >= DateTime.UtcNow.AddDays(-MaxFileCachedDuration))
// have reduced the number of items below the maximum allowed.
if (count <= MaxFilesCount - 1)
{
break;
}
try
// Remove from the cache and delete each CachedImage.
string key = Path.GetFileNameWithoutExtension(fileInfo.Name);
if (await CacheIndexer.Instance.RemoveAsync(key))
{
// Remove from the cache and delete each CachedImage.
FileInfo fileInfo = new FileInfo(image.Path);
string key = Path.GetFileNameWithoutExtension(fileInfo.Name);
if (await MemoryCache.Instance.RemoveAsync(key))
{
fileInfo.Delete();
groupCount -= 1;
}
}
// ReSharper disable EmptyGeneralCatchClause
catch
// ReSharper restore EmptyGeneralCatchClause
{
// Do nothing; skip to the next file.
fileInfo.Delete();
count -= 1;
}
}
// ReSharper disable once EmptyGeneralCatchClause
catch
{
// Do nothing; skip to the next file.
}
}
}
@ -376,8 +360,8 @@ namespace ImageProcessor.Web.Caching
/// Gets the full transformed cached path for the image.
/// The images are stored in paths that are based upon the sha1 of their full request path
/// taking the individual characters of the hash to determine their location.
/// This allows us to store 40 folders within 40 folders giving us a total of 3.0223145e+64 potential images.
/// Answers on a post card if you can figure out a way to store their details in a db for fast recovery.
/// This allows us to store millions of images.
/// Answers on a post card if you can figure out a way to store that many details in a db for fast recovery.
/// </summary>
/// <returns>The full cached path for the image.</returns>
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
@ -394,7 +378,8 @@ namespace ImageProcessor.Web.Caching
string fallbackExtension = this.imageName.Substring(this.imageName.LastIndexOf(".", StringComparison.Ordinal) + 1);
string encryptedName = this.fullPath.ToSHA1Fingerprint();
string pathFromKey = string.Join("\\", encryptedName.ToCharArray());
// Collision rate of about 1 in 1000 for the folder structure.
string pathFromKey = string.Join("\\", encryptedName.ToCharArray().Take(5));
string cachedFileName = string.Format(
"{0}.{1}",

12
src/ImageProcessor.Web/NET45/Caching/CacheManager.cs → src/ImageProcessor.Web/NET45/Caching/MemCache.cs

@ -1,10 +1,10 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CacheManager.cs" company="James South">
// <copyright file="MemCache.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates methods that allow the caching and retrieval of objects.
// Encapsulates methods that allow the caching and retrieval of objects from the in memory cache.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
@ -17,15 +17,15 @@ namespace ImageProcessor.Web.Caching
#endregion
/// <summary>
/// Encapsulates methods that allow the caching and retrieval of objects.
/// Encapsulates methods that allow the caching and retrieval of objects from the in memory cache.
/// </summary>
public static class CacheManager
internal static class MemCache
{
#region Fields
/// <summary>
/// The cache
/// </summary>
private static readonly ObjectCache Cache = System.Runtime.Caching.MemoryCache.Default;
private static readonly ObjectCache Cache = MemoryCache.Default;
/// <summary>
/// An internal list of cache keys to allow bulk removal.
@ -99,8 +99,6 @@ namespace ImageProcessor.Web.Caching
return Cache.Get(key, regionName);
}
//public static bool
/// <summary>
/// Updates an item to the cache.
/// </summary>

42
src/ImageProcessor.Web/NET45/Caching/SQLContext.cs

@ -1,24 +1,21 @@
// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SQLContext.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// -----------------------------------------------------------------------
// <summary>
// Provides a wrapper for the SQLite functionality.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Caching
{
#region Using
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Web.Hosting;
using ImageProcessor.Web.Config;
using ImageProcessor.Web.Helpers;
using SQLite;
#endregion
/// <summary>
@ -79,31 +76,6 @@ namespace ImageProcessor.Web.Caching
}
}
/// <summary>
/// Gets all the images from the database.
/// </summary>
/// <returns>
/// The <see cref="System.Collections.Generic.List{CleanupImage}"/>.
/// </returns>
internal static List<CleanupImage> GetImagesForCleanup()
{
try
{
List<CleanupImage> images;
using (SQLiteConnection connection = new SQLiteConnection(ConnectionString))
{
images = connection.Query<CleanupImage>("SELECT Path,ExpiresUtc FROM CachedImage");
}
return images;
}
catch
{
return new List<CleanupImage>();
}
}
/// <summary>
/// Gets a cached image from the database.
/// </summary>

36
src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs

@ -20,12 +20,10 @@ namespace ImageProcessor.Web.HttpModules
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
using System.Web.Security;
using ImageProcessor.Helpers.Extensions;
using ImageProcessor.Imaging;
using ImageProcessor.Web.Caching;
@ -53,16 +51,6 @@ namespace ImageProcessor.Web.HttpModules
/// The assembly version.
/// </summary>
private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
/// <summary>
/// The value that acts as a basis to check that the startup code has only been ran once.
/// </summary>
private static int initCheck;
/// <summary>
/// A value indicating whether the application has started.
/// </summary>
private readonly bool hasModuleInitialized = initCheck == 1;
#endregion
#region IHttpModule Members
@ -76,12 +64,6 @@ namespace ImageProcessor.Web.HttpModules
/// </param>
public void Init(HttpApplication context)
{
if (!this.hasModuleInitialized)
{
Interlocked.CompareExchange(ref initCheck, 1, 0);
// DiskCache.CreateDirectories();
}
#if NET45
EventHandlerTaskAsyncHelper wrapper = new EventHandlerTaskAsyncHelper(this.PostAuthorizeRequest);
@ -288,6 +270,8 @@ namespace ImageProcessor.Web.HttpModules
// Only process if the file has been updated.
if (isNewOrUpdated)
{
string cachedPath = cache.CachedPath;
// Process the image.
using (ImageFactory imageFactory = new ImageFactory())
{
@ -308,21 +292,21 @@ namespace ImageProcessor.Web.HttpModules
{
if (responseStream != null)
{
// Trim the cache.
await cache.TrimCachedFoldersAsync();
responseStream.CopyTo(memoryStream);
imageFactory.Load(memoryStream)
.AddQueryString(queryString)
.Format(ImageUtils.GetImageFormat(imageName))
.AutoProcess().Save(cache.CachedPath);
.AutoProcess().Save(cachedPath);
// Ensure that the LastWriteTime property of the source and cached file match.
DateTime dateTime = await cache.SetCachedLastWriteTimeAsync();
// Add to the cache.
await cache.AddImageToCacheAsync(dateTime);
// Trim the cache.
await cache.TrimCachedFolderAsync(cachedPath);
}
}
}
@ -330,16 +314,16 @@ namespace ImageProcessor.Web.HttpModules
}
else
{
// Trim the cache.
await cache.TrimCachedFoldersAsync();
imageFactory.Load(fullPath).AutoProcess().Save(cache.CachedPath);
imageFactory.Load(fullPath).AutoProcess().Save(cachedPath);
// Ensure that the LastWriteTime property of the source and cached file match.
DateTime dateTime = await cache.SetCachedLastWriteTimeAsync();
// Add to the cache.
await cache.AddImageToCacheAsync(dateTime);
// Trim the cache.
await cache.TrimCachedFolderAsync(cachedPath);
}
}
}

5
src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj

@ -50,10 +50,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Caching\CachedImage.cs" />
<Compile Include="Caching\CacheManager.cs" />
<Compile Include="Caching\CleanupImage.cs" />
<Compile Include="Caching\MemCache.cs" />
<Compile Include="Caching\DiskCache.cs" />
<Compile Include="Caching\MemoryCache.cs" />
<Compile Include="Caching\CacheIndexer.cs" />
<Compile Include="Caching\SQLContext.cs" />
<Compile Include="Config\ImageCacheSection.cs" />
<Compile Include="Config\ImageProcessingSection.cs" />

4
src/ImageProcessor.Web/NET45/Properties/AssemblyInfo.cs

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("2.3.0.6")]
[assembly: AssemblyFileVersion("2.3.0.6")]
[assembly: AssemblyVersion("2.4.0.0")]
[assembly: AssemblyFileVersion("2.4.0.0")]

4
src/ImageProcessor/Properties/AssemblyInfo.cs

@ -32,6 +32,6 @@ using System.Security;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.7.1.1")]
[assembly: AssemblyFileVersion("1.7.1.1")]
[assembly: AssemblyVersion("1.8.0.0")]
[assembly: AssemblyFileVersion("1.8.0.0")]

4
src/TestWebsites/NET45/Test_Website_NET45/Test_Website_NET45.csproj

@ -193,7 +193,9 @@
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="config\imageprocessor\processing.config" />
<Content Include="config\imageprocessor\processing.config">
<SubType>Designer</SubType>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="config\imageprocessor\security.config" />

5
src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml

@ -141,7 +141,7 @@
<article>
<h1>Color Profiles</h1>
<section>
@* <section>
<div class="row">
<div class="col-s-6">
<h2>CMYK original jpg</h2>
@ -151,9 +151,8 @@
<h2>sRGB original jpg</h2>
<img src="/images/srgb.jpg?" width="400" />
</div>
</div>
</section>
</section>*@
<section>
<div class="row">
<div class="col-s-6">

1
src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Png.cshtml

@ -8,7 +8,6 @@
<div class="col-s-6">
<h2>Resized</h2>
<img src="/images/Penguins.png?width=300" />
<img src="/gifts/cmyk.png?x=300" />
</div>
<div class="col-s-6">
<h2>Cropped </h2>

Loading…
Cancel
Save