Improved image endpoints, added size to ImageController
This commit is contained in:
parent
0491678055
commit
21afbf5a39
|
@ -1,5 +1,6 @@
|
||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Wave.Services;
|
using Wave.Services;
|
||||||
|
|
||||||
namespace Wave.Controllers;
|
namespace Wave.Controllers;
|
||||||
|
@ -7,14 +8,15 @@ namespace Wave.Controllers;
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/images")]
|
[Route("/images")]
|
||||||
public class ImageController(ImageService imageService) : ControllerBase {
|
public class ImageController(ImageService imageService) : ControllerBase {
|
||||||
private ImageService ImageService { get; } = imageService;
|
private ImageService ImageService { get; } = imageService;
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{imageId:guid}")]
|
[Route("{imageId:guid}")]
|
||||||
public IActionResult Get(Guid imageId) {
|
public async Task<IActionResult> Get(Guid imageId, [FromQuery, Range(16, 800)] int size = 800) {
|
||||||
string? path = ImageService.GetPath(imageId);
|
string? path = ImageService.GetPath(imageId);
|
||||||
|
|
||||||
if (path is null) return NotFound();
|
if (path is null) return NotFound();
|
||||||
return File(System.IO.File.OpenRead(path), ImageService.ImageMimeType);
|
if (size < 800) return File(await ImageService.GetResized(path, size), ImageService.ImageMimeType);
|
||||||
}
|
return File(System.IO.File.OpenRead(path), ImageService.ImageMimeType);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using ImageMagick;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.OutputCaching;
|
using Microsoft.AspNetCore.OutputCaching;
|
||||||
|
@ -31,15 +30,7 @@ public class UserController(ImageService imageService, IDbContextFactory<Applica
|
||||||
string? path = ImageService.GetPath(user.ProfilePicture.ImageId);
|
string? path = ImageService.GetPath(user.ProfilePicture.ImageId);
|
||||||
if (path is null) return NotFound();
|
if (path is null) return NotFound();
|
||||||
|
|
||||||
if (size < 800) {
|
if (size < 800) return File(await ImageService.GetResized(path, size), ImageService.ImageMimeType);
|
||||||
var image = new MagickImage(path);
|
|
||||||
image.Resize(new MagickGeometry(size));
|
|
||||||
using var memory = new MemoryStream();
|
|
||||||
await image.WriteAsync(memory);
|
|
||||||
|
|
||||||
return File(memory.GetBuffer(), ImageService.ImageMimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return File(System.IO.File.OpenRead(path), ImageService.ImageMimeType);
|
return File(System.IO.File.OpenRead(path), ImageService.ImageMimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,56 +1,62 @@
|
||||||
using ImageMagick;
|
using ImageMagick;
|
||||||
using static System.Net.Mime.MediaTypeNames;
|
|
||||||
|
|
||||||
namespace Wave.Services;
|
namespace Wave.Services;
|
||||||
|
|
||||||
public class ImageService(ILogger<ImageService> logger) {
|
public class ImageService(ILogger<ImageService> logger) {
|
||||||
private ILogger<ImageService> Logger { get; } = logger;
|
private ILogger<ImageService> Logger { get; } = logger;
|
||||||
private const string BasePath = "./files/images";
|
private const string BasePath = "./files/images";
|
||||||
private const string ImageExtension = ".jpg";
|
private const string ImageExtension = ".jpg";
|
||||||
public string ImageMimeType => "image/jpg";
|
public string ImageMimeType => "image/jpg";
|
||||||
|
|
||||||
public string? GetPath(Guid imageId) {
|
public string? GetPath(Guid imageId) {
|
||||||
string path = Path.Combine(BasePath, imageId + ImageExtension);
|
string path = Path.Combine(BasePath, imageId + ImageExtension);
|
||||||
return File.Exists(path) ? path : null;
|
return File.Exists(path) ? path : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Guid?> StoreImageAsync(string temporaryPath, int size = 800, CancellationToken cancellation = default) {
|
public async Task<byte[]> GetResized(string path, int size) {
|
||||||
if (File.Exists(temporaryPath) is not true) return null;
|
var image = new MagickImage(path);
|
||||||
|
image.Resize(new MagickGeometry(size));
|
||||||
|
using var memory = new MemoryStream();
|
||||||
|
await image.WriteAsync(memory);
|
||||||
|
return memory.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
public async ValueTask<Guid?> StoreImageAsync(string temporaryPath, int size = 800,
|
||||||
if (File.Exists(BasePath) is not true) Directory.CreateDirectory(BasePath);
|
CancellationToken cancellation = default) {
|
||||||
|
if (File.Exists(temporaryPath) is not true) return null;
|
||||||
|
|
||||||
var image = new MagickImage();
|
try {
|
||||||
await image.ReadAsync(temporaryPath, cancellation);
|
if (File.Exists(BasePath) is not true) Directory.CreateDirectory(BasePath);
|
||||||
|
|
||||||
// Jpeg with 90% compression should look decent
|
var image = new MagickImage();
|
||||||
image.Resize(new MagickGeometry(size)); // this preserves aspect ratio
|
await image.ReadAsync(temporaryPath, cancellation);
|
||||||
image.Format = MagickFormat.Jpeg;
|
|
||||||
image.Quality = 90;
|
|
||||||
|
|
||||||
if (image.GetExifProfile() is not null) {
|
// Jpeg with 90% compression should look decent
|
||||||
image.RemoveProfile(image.GetExifProfile());
|
image.Resize(new MagickGeometry(size)); // this preserves aspect ratio
|
||||||
}
|
image.Format = MagickFormat.Jpeg;
|
||||||
|
image.Quality = 90;
|
||||||
|
|
||||||
// Overwrite exif for privacy reasons
|
if (image.GetExifProfile() is { } exifProfile) image.RemoveProfile(exifProfile);
|
||||||
var exif = new ExifProfile {
|
|
||||||
Parts = ExifParts.None
|
|
||||||
};
|
|
||||||
exif.CreateThumbnail();
|
|
||||||
image.SetProfile(exif);
|
|
||||||
|
|
||||||
var guid = Guid.NewGuid();
|
|
||||||
string path = Path.Combine(BasePath, guid + ImageExtension);
|
|
||||||
await image.WriteAsync(path, cancellation);
|
|
||||||
return guid;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.LogInformation(ex, "Failed to process uploaded image.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(Guid imageId) {
|
// Overwrite exif for privacy reasons
|
||||||
string path = Path.Combine(BasePath, imageId + ImageExtension);
|
var exif = new ExifProfile {
|
||||||
File.Delete(path);
|
Parts = ExifParts.None
|
||||||
}
|
};
|
||||||
|
exif.CreateThumbnail();
|
||||||
|
image.SetProfile(exif);
|
||||||
|
|
||||||
|
var guid = Guid.NewGuid();
|
||||||
|
string path = Path.Combine(BasePath, guid + ImageExtension);
|
||||||
|
await image.WriteAsync(path, cancellation);
|
||||||
|
return guid;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.LogInformation(ex, "Failed to process uploaded image.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(Guid imageId) {
|
||||||
|
string path = Path.Combine(BasePath, imageId + ImageExtension);
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue