Skip to content

Commit

Permalink
VCST-2006: Add active image format configuration (#113)
Browse files Browse the repository at this point in the history
feat: Added active image format configuration to resolve files by extension and content type. By default, the following formats are enabled: JPEG, PNG, WebP. The system supports the following image formats: BMP, GIF, JPEG, PBM, PNG, TIFF, TGA, WebP. For a complete list of image formats supported by ImageSharp, please visit this link: https://docs.sixlabors.com/articles/imagesharp/imageformats.html
  • Loading branch information
OlegoO authored Oct 22, 2024
1 parent 341a99e commit 1766061
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 98 deletions.
49 changes: 27 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,38 @@

[![CI status](https://github.com/VirtoCommerce/vc-module-image-tools/workflows/Module%20CI/badge.svg?branch=dev)](https://github.com/VirtoCommerce/vc-module-image-tools/actions?query=workflow%3A"Module+CI") [![Quality gate](https://sonarcloud.io/api/project_badges/measure?project=VirtoCommerce_vc-module-image-tools&metric=alert_status&branch=dev)](https://sonarcloud.io/dashboard?id=VirtoCommerce_vc-module-image-tools) [![Reliability rating](https://sonarcloud.io/api/project_badges/measure?project=VirtoCommerce_vc-module-image-tools&metric=reliability_rating&branch=dev)](https://sonarcloud.io/dashboard?id=VirtoCommerce_vc-module-image-tools) [![Security rating](https://sonarcloud.io/api/project_badges/measure?project=VirtoCommerce_vc-module-image-tools&metric=security_rating&branch=dev)](https://sonarcloud.io/dashboard?id=VirtoCommerce_vc-module-image-tools) [![Sqale rating](https://sonarcloud.io/api/project_badges/measure?project=VirtoCommerce_vc-module-image-tools&metric=sqale_rating&branch=dev)](https://sonarcloud.io/dashboard?id=VirtoCommerce_vc-module-image-tools)

VirtoCommerce.ImageTools module represents a functionality that helps working with images. This module allows generating thumbnails, which can be used for upload instead of the original images.

## Key features

1. Possibility to make different image thumbnails and use them, instead of using the original images. For example, this functionality can be used in listing or for previews;
1. Possibility to resize Group images into tasks and run them against an asset catalog in the background;
1. Supports image formats: Bmp, Gif, Jpeg, Pbm, Png, Tiff, Tga, WebP;

![image](https://user-images.githubusercontent.com/20122385/36428926-e483c5e6-1659-11e8-88aa-e4dc2b95b50b.png)

## Documentation

[Image Tools Module Document](/docs/index.md)

[View on GitHub](https://github.com/VirtoCommerce/vc-module-image-tools)
The ImageTools module provides functionality for working with images. It allows generating thumbnails that can be used for uploading instead of the original images.

## Key Features

1. Generate different image thumbnails and use them instead of the original images. This functionality is useful for listings or previews.
2. Resize group images into tasks and run them against an asset catalog in the background.
3. Utilize the [Sixlabors.ImageSharp library](https://docs.sixlabors.com/articles/imagesharp/index.html) for image processing.
4. Default image formats supported:
- Jpeg
- Png
- WebP
5. Additional image formats supported (can be enabled in thumbnail settings):
- Jpeg
- Bmp
- WebP
- Gif
- Pbm
- Png
- Tiff
- Tga

![ImageTools Main Screen](docs/media/main-page.png)

## References

* Deploy: https://docs.virtocommerce.org/developer-guide/deploy-module-from-source-code/
* Installation: https://docs.virtocommerce.org/user-guide/modules/
* Home: https://virtocommerce.com
* Community: https://www.virtocommerce.org
* [Download Latest Release](https://github.com/VirtoCommerce/vc-module-image-tools/releases)
- Home: https://virtocommerce.com
- Documentation: https://docs.virtocommerce.org
- Community: https://www.virtocommerce.org
- [Download Latest Release](https://github.com/VirtoCommerce/vc-module-image-tools/releases)

## License

Copyright (c) Virto Solutions LTD. All rights reserved.
Copyright (c) Virto Solutions LTD. All rights reserved.

Licensed under the Virto Commerce Open Software License (the "License"); you
may not use this file except in compliance with the License. You may
Expand Down
Binary file added docs/media/main-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 20 additions & 1 deletion src/VirtoCommerce.ImageToolsModule.Core/ModuleConstants.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Webp;
using VirtoCommerce.Platform.Core.Settings;

namespace VirtoCommerce.ImageToolsModule.Core
Expand All @@ -17,7 +20,7 @@ public static class Permissions
Update = "thumbnail:update",
Read = "thumbnail:read";

public static string[] AllPermissions { get; } = { Access, Create, Delete, Update, Read };
public static string[] AllPermissions { get; } = [Access, Create, Delete, Update, Read];
}
}

Expand Down Expand Up @@ -57,6 +60,21 @@ public static class General
DefaultValue = 50,
};

public static SettingDescriptor AllowedImageFormats { get; } = new()
{
Name = "ImageTools.Thumbnails.AllowedImageFormats",
GroupName = "Thumbnail|General",
ValueType = SettingValueType.ShortText,
DefaultValue = string.Empty,
IsDictionary = true,
AllowedValues =
[
JpegFormat.Instance.Name,
PngFormat.Instance.Name,
WebpFormat.Instance.Name,
],
};

public static IEnumerable<SettingDescriptor> AllGeneralSettings
{
get
Expand All @@ -65,6 +83,7 @@ public static IEnumerable<SettingDescriptor> AllGeneralSettings
yield return EventBasedThumbnailGeneration;
yield return ImageProcessJobCronExpression;
yield return ProcessBatchSize;
yield return AllowedImageFormats;
}
}
}
Expand Down
17 changes: 15 additions & 2 deletions src/VirtoCommerce.ImageToolsModule.Core/Services/IImageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ namespace VirtoCommerce.ImageToolsModule.Core.Services
public interface IImageService
{
/// <summary>
///Loads Image from blob storage
/// Loads Image from blob storage
/// </summary>
/// <param name="imageUrl">image url.</param>
/// <param name="format">image format.</param>
/// <returns>Image object.</returns>
Task<Image<Rgba32>> LoadImageAsync(string imageUrl);

Expand All @@ -27,5 +26,19 @@ public interface IImageService
/// <param name="format">Image object format.</param>
/// <param name="jpegQuality">Target image quality.</param>
Task SaveImageAsync(string imageUrl, Image<Rgba32> image, IImageFormat format, JpegQuality jpegQuality);

/// <summary>
/// Defines if given file extension is allowed for image processing
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
Task<bool> IsFileExtensionAllowedAsync(string path);

/// <summary>
/// Defines if given image format is allowed for image processing
/// </summary>
/// <param name="format"></param>
/// <returns></returns>
Task<bool> IsImageFormatAllowedAsync(IImageFormat format);
}
}
Original file line number Diff line number Diff line change
@@ -1,47 +1,63 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using VirtoCommerce.AssetsModule.Core.Assets;
using VirtoCommerce.ImageToolsModule.Core;
using VirtoCommerce.ImageToolsModule.Core.Models;
using VirtoCommerce.ImageToolsModule.Core.Services;
using VirtoCommerce.Platform.Core.Common;
using VirtoCommerce.Platform.Core.Settings;

namespace VirtoCommerce.ImageToolsModule.Data.Services
{
public class DefaultImageService : IImageService
{
private readonly IBlobStorageProvider _storageProvider;
private readonly ISettingsManager _settingsManager;
private readonly ILogger<DefaultImageService> _logger;

public DefaultImageService(IBlobStorageProvider storageProvider, ILogger<DefaultImageService> logger)
private IList<IImageFormat> _allowedImageFormats;
private readonly StringComparer _ignoreCase = StringComparer.OrdinalIgnoreCase;

public DefaultImageService(IBlobStorageProvider storageProvider, ISettingsManager settingsManager, ILogger<DefaultImageService> logger)
{
_storageProvider = storageProvider;
_settingsManager = settingsManager;
_logger = logger;
}

/// <summary>
/// Load to Image from blob.
/// </summary>
/// <param name="imageUrl">image url.</param>
/// <param name="format">image format.</param>
/// <returns>Image object.</returns>
public virtual async Task<Image<Rgba32>> LoadImageAsync(string imageUrl)
{
_logger.LogInformation($"Loading image {imageUrl}");
_logger.LogInformation("Loading image {imageUrl}", imageUrl);

try
{
using var blobStream = _storageProvider.OpenRead(imageUrl);
return await Image.LoadAsync<Rgba32>(blobStream);
await using var blobStream = await _storageProvider.OpenReadAsync(imageUrl);
var imageFormat = await Image.DetectFormatAsync(blobStream);

if (await IsImageFormatAllowedAsync(imageFormat))
{
return await Image.LoadAsync<Rgba32>(blobStream);
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Could not load image {imageUrl}");
return null!;
_logger.LogError(ex, "Could not load image {imageUrl}", imageUrl);
}

return null!;
}

/// <inheritdoc />
Expand All @@ -57,7 +73,7 @@ public virtual async Task SaveImageAsync(string imageUrl, Image<Rgba32> image, I
await using var blobStream = await _storageProvider.OpenWriteAsync(imageUrl);
using var stream = new MemoryStream();

if (format.DefaultMimeType == "image/jpeg")
if (format.DefaultMimeType == JpegFormat.Instance.DefaultMimeType)
{
var options = new JpegEncoder
{
Expand All @@ -74,5 +90,38 @@ public virtual async Task SaveImageAsync(string imageUrl, Image<Rgba32> image, I
stream.Position = 0;
await stream.CopyToAsync(blobStream);
}

public virtual async Task<bool> IsFileExtensionAllowedAsync(string path)
{
var allowedImageFormats = await GetAllowedImageFormats();
var extension = Path.GetExtension(path).TrimStart('.');

return allowedImageFormats
.SelectMany(x => x.FileExtensions)
.Contains(extension, _ignoreCase);
}

public virtual async Task<bool> IsImageFormatAllowedAsync(IImageFormat format)
{
var allowedImageFormats = await GetAllowedImageFormats();

return allowedImageFormats.Any(x => x.Name.EqualsIgnoreCase(format.Name));
}


private async Task<IList<IImageFormat>> GetAllowedImageFormats()
{
if (_allowedImageFormats == null)
{
var allowedImageFormatsSetting = await _settingsManager.GetObjectSettingAsync(ModuleConstants.Settings.General.AllowedImageFormats.Name);
var allowedFormatNames = allowedImageFormatsSetting.AllowedValues.OfType<string>().ToArray();

_allowedImageFormats = Configuration.Default.ImageFormats
.Where(x => allowedFormatNames.Contains(x.Name, _ignoreCase))
.ToArray();
}

return _allowedImageFormats;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
Expand All @@ -19,19 +18,20 @@ public class BlobImagesChangesProvider : IImagesChangesProvider
{
public bool IsTotalCountSupported => true;

private static readonly string[] _supportedImageExtensions = { ".bmp", ".gif", ".jpg", ".jpeg", ".png", ".webp", ".pbm" };

private readonly IBlobStorageProvider _storageProvider;
private readonly IThumbnailOptionSearchService _thumbnailOptionSearchService;
private readonly IImageService _imageService;
private readonly IPlatformMemoryCache _platformMemoryCache;

public BlobImagesChangesProvider(
IBlobStorageProvider storageProvider,
IThumbnailOptionSearchService thumbnailOptionSearchService,
IImageService imageService,
IPlatformMemoryCache platformMemoryCache)
{
_storageProvider = storageProvider;
_thumbnailOptionSearchService = thumbnailOptionSearchService;
_imageService = imageService;
_platformMemoryCache = platformMemoryCache;
}

Expand Down Expand Up @@ -94,9 +94,12 @@ protected virtual async Task<ConcurrentDictionary<string, BlobEntry>> ReadBlobFo
var searchResults = await _storageProvider.SearchAsync(folderPath, null);

// Add supported images
foreach (var imageBlob in searchResults.Results.Where(IsSupportedImage))
foreach (var imageBlob in searchResults.Results)
{
result.TryAdd(imageBlob.Url, imageBlob);
if (await IsSupportedImage(imageBlob))
{
result.TryAdd(imageBlob.Url, imageBlob);
}
}

// Add images from child folders recursively
Expand All @@ -113,9 +116,9 @@ await Parallel.ForEachAsync(searchResults.Results.Where(IsFolder), async (folder
return result;
}

protected virtual bool IsSupportedImage(BlobEntry blobEntry)
protected virtual Task<bool> IsSupportedImage(BlobEntry blobEntry)
{
return _supportedImageExtensions.Contains(Path.GetExtension(blobEntry.Name), StringComparer.OrdinalIgnoreCase);
return _imageService.IsFileExtensionAllowedAsync(blobEntry.Name);
}

protected virtual bool IsFolder(BlobEntry blobEntry)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@
"ImageTools.Thumbnails.EventBasedThumbnailGeneration": {
"title": "Enable event based thumbnail generation",
"description": "Start thumbnail generation as soon as an asset is added"
},
"ImageTools.Thumbnails.AllowedImageFormats": {
"title": "Allowed image formats",
"description": "Thumbnails will only be generated for the specified image formats. For a list of image formats supported by ImageSharp, please visit the following link: https://docs.sixlabors.com/articles/imagesharp/imageformats.html"
}
},
"module": {
Expand Down
Loading

0 comments on commit 1766061

Please sign in to comment.