Zaczynamy od:
class ImageSize {
public int Width {get;set;}
public int Height {get;set;}
}
class DimensionScale{
public int Value {get;}
public int Crop {get;}
public DimensionScale(int destinationValue){
Value = destValue;
Crop = 0;
}
public DimensionScale(int originalValue, int destValue, decimal ratio){
Value = (int)(originalValue * ratio);
Crop = Value - destValue;
}
}
class ScalingParameters{
public DimensionScale Width {get;set;}
public DimensionScale Height {get;set;}
}
private static void ThrowIfCannotScale(ImageSize picture, ImageSize thumbnail){
// You should also check for null values here
if (picture.Height < thumbnail.Height || picture.Width < thumbnail.Width)
throw new CannotScaleException($"Actula size cannot be less than target size. Original width = {picture.Width}, height = {picture.Height}. Destination width = {thumbnail.Width}, height = {thumbnail.Height}");
}
private decimal Ratio(int length1, int length2){
return (decimal)length1 / length2;
}
private bool ScalingNeeded(ImageSize picture, ImageSize thumbnail){
bool areOfSameSize = picture.Height == thumbnail.Height && picture.Width == thumbnail.Width;
bool areOfSameRatio = Ratio(picture.Height, thumbnail.Height) == Ratio(picture.Width, thumbnail.Width);
return !(areOfSameRatio || areOfSameRatio);
}
private static GenerateThumbnail(ScalingParameters scaling){
var newStream = DrawImageService.DrawThumbnail(
scaling.Height.Value, scaling.Width.Value,
scaling.Height.Crop, scaling.WIdth.Crop,
Image.Stream);
Thumbnail = new Thumbnail(newStream, Image.Name);
}
private bool ShouldScaleByWidth(ImageSize picture, ImageSize thumbnail){
return Ratio(picture.Height, Thumbnail.Height) > Ratio(picture.Width, thumbnail.Width);
}
public static void GenerateThumbnailFromImage(ThumbnailSize size)
{
Image image = System.Drawing.Image.FromStream(Image.Stream);
var pictureSize = new ImageSize {Width = image.Width, Height = image.Height};
var thumbnailSize = new ImageSize {Width = size.Width, Height = size.Height};
ThrowIfCannotScale(pictureSize, thumbnailSize);
if(!ScalingNeeded(pictureSize, thumbnailSize){
GenerateThumbnail(new ScalingParameters{
Width = new DimensionScale(thumbnail.Width),
Height = new DimensionScale(thumbnail.Height)
});
}else if(ShouldScaleByWidth(pictureSize, thumbnailSize)){
GenerateThumbnail(new ScalingParameters{
Height = new DimensionScale(thumbnail.Height),
Width = new DimensionScale(picture.Width, thumbnail.Width, Ratio(pictureSize.Heigth, thumbnailSize.Height)),
});
}else{
GenerateThumbnail(new ScalingParameters{
Height = new DimensionScale(pictureHeight, thumbnail.Height, Ratio(pictureSize.Width, thumbnailSize.Width)),
Width = new DimensionScale(thumbnail.Width),
});
}
}
Dla mnie wystarczy do szczęścia, ale gdy czujemy, że będzie więcej rodzajów skalowań, to każdą gałąź ifa wydziela do czegoś takiego:
interface IScalingStrategy{
ScalingParameters Scale(ImageSize picture, ImageSize thumbnail);
}
class NoScaling : IScalingStrategy{
ScalingParameters Scale(ImageSize picture, ImageSize thumbnail){
if(ScalingNeeded(picture, thumbnail)){
return null;
}
return new ScalingParameters{
Width = new DimensionScale(thumbnail.Width),
Height = new DimensionScale(thumbnail.Height)
};
}
}
class WidthScaling : IScalingStrategy{
ScalingParameters Scale(ImageSize picture, ImageSize thumbnail){
if(!ShouldScaleByWidth(picture, thumbnail)){
return null;
}
return new ScalingParameters{
Height = new DimensionScale(thumbnail.Height),
Width = new DimensionScale(picture.Width, thumbnail.Width, Ratio(pictureSize.Heigth, thumbnailSize.Height)),
};
}
}
class HeightScaling : IScalingStrategy{
ScalingParameters Scale(ImageSize picture, ImageSize thumbnail){
return new ScalingParameters{
Height = new DimensionScale(pictureHeight, thumbnail.Height, Ratio(pictureSize.Width, thumbnailSize.Width)),
Width = new DimensionScale(thumbnail.Width),
};
}
}
Co w efekcie daje:
public static void GenerateThumbnailFromImage(ThumbnailSize size)
{
Image image = System.Drawing.Image.FromStream(Image.Stream);
var pictureSize = new ImageSize {Width = image.Width, Height = image.Height};
var thumbnailSize = new ImageSize {Width = size.Width, Height = size.Height};
ThrowIfCannotScale(pictureSize, thumbnailSize);
var scalers = new []{new NoScaling(), new WidthScaling(), new HeightScaling()};
GenerateThumbnail(
scalers.Map(s => s.Scale(pictureSize, thumbnailSize)).First(s => s != null)
);
}
Oczywiście interfejs jest paskudny, ale u mnie jest niedzielny poranek, więc nie chce mi się go rozbudowywać. Mógłby mieć dwie metody: canScale i Scale, ale to już polerowanie kodu.
Zalety? Jak trzeba nowy rodzaj skalowania, to dokładasz nową strategię i gotowe. Strategie można testować jednostkowo. Metody pomocnicze lecą do jakiejś klasy statycznej, a całą metodę GenerateThumbnail opakowujemy w coś odczytującego pola statyczne i zapisującego je, co daje kompletnie odseparowany kawałek kodu.
Nie kompilowałem, nie testowałem, pokazuję tylko ideę.