Я работаю над сайтом, который будет продавать ювелирные изделия ручной работы, и я заканчиваю редактор изображений, но он ведет себя не совсем правильно.
Обычно пользователь загружает изображение, которое будет сохранено в качестве источника, а затем оно будет изменено в соответствии с экраном пользователя и сохранено в качестве временного. Затем пользователь перейдет к экрану, который позволит им обрезать изображение, а затем сохранить его в окончательной версии.
Все это прекрасно работает, за исключением того, что в финальной версии есть 3 ошибки. Первая - черная горизонтальная линия в самом низу изображения. Во-вторых, это своего рода контур, который следует за краями. Я думал, что это потому, что я снижал качество, но даже при 100% оно все еще проявляется ... И, наконец, я заметил, что обрезанное изображение всегда на пару пикселей ниже того, что я указываю ...
В любом случае, я надеюсь, что кто-то, кто имеет опыт редактирования изображений с C #, может, возможно, взглянет на код и посмотрит, куда я могу пойти по правильному пути.
О, кстати, это в приложении ASP.NET MVC.
Вот код:
public class ImageProvider {
private readonly ProductProvider ProductProvider = null;
private readonly EncoderParameters HighQualityEncoder = new EncoderParameters();
private readonly ImageCodecInfo JpegCodecInfo = ImageCodecInfo.GetImageEncoders().Single(
c => (c.MimeType == "image/jpeg"));
private readonly string Path = HttpContext.Current.Server.MapPath("~/Resources/Images/Products");
private readonly short[][] Dimensions = new short[3][] {
new short[2] { 640, 480 },
new short[2] { 240, 0 },
new short[2] { 80, 60 }
}
public ImageProvider(ProductProvider ProductProvider) {
this.ProductProvider = ProductProvider;
HighQualityEncoder.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
}
public void Crop(string FileName, Image Image, Crop Crop) {
using (Bitmap Source = new Bitmap(Image)) {
using (Bitmap Target = new Bitmap(Crop.Width, Crop.Height)) {
using (Graphics Graphics = Graphics.FromImage(Target)) {
Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
Graphics.SmoothingMode = SmoothingMode.HighQuality;
Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
Graphics.CompositingQuality = CompositingQuality.HighQuality;
Graphics.DrawImage(Source, new Rectangle(0, 0, Target.Width, Target.Height), new Rectangle(Crop.Left, Crop.Top, Crop.Width, Crop.Height), GraphicsUnit.Pixel);
}
Target.Save(FileName, JpegCodecInfo, HighQualityEncoder);
}
}
}
public void CropAndResize(Product Product, Crop Crop) {
using (Image Source = Image.FromFile(String.Format("{0}/{1}.source", Path, Product.ProductId))) {
using (Image Temp = Image.FromFile(String.Format("{0}/{1}.temp", Path, Product.ProductId))) {
float Percent = ((float)Source.Width / (float)Temp.Width);
short Width = (short)(Temp.Width * Percent);
short Height = (short)(Temp.Height * Percent);
Crop.Height = (short)(Crop.Height * Percent);
Crop.Left = (short)(Crop.Left * Percent);
Crop.Top = (short)(Crop.Top * Percent);
Crop.Width = (short)(Crop.Width * Percent);
Img Img = new Img();
this.ProductProvider.AddImageAndSave(Product, Img);
this.Crop(String.Format("{0}/{1}.cropped", Path, Img.ImageId), Source, Crop);
using (Image Cropped = Image.FromFile(String.Format("{0}/{1}.cropped", Path, Img.ImageId))) {
this.Resize(this.Dimensions[0], String.Format("{0}/{1}-L.jpg", Path, Img.ImageId), Cropped, HighQualityEncoder);
this.Resize(this.Dimensions[1], String.Format("{0}/{1}-T.jpg", Path, Img.ImageId), Cropped, HighQualityEncoder);
this.Resize(this.Dimensions[2], String.Format("{0}/{1}-S.jpg", Path, Img.ImageId), Cropped, HighQualityEncoder);
}
}
}
this.Purge(Product);
}
public void QueueFor( Product Product, HttpPostedFileBase PostedFile) {
using (Image Image = Image.FromStream(PostedFile.InputStream)) {
this.Resize(new short[2] {
1152,
0
}, String.Format("{0}/{1}.temp", Path, Product.ProductId), Image, HighQualityEncoder);
}
PostedFile.SaveAs(String.Format("{0}/{1}.source", Path, Product.ProductId));
}
private void Purge(Product Product) {
string Source = String.Format("{0}/{1}.source", Path, Product.ProductId);
string Temp = String.Format("{0}/{1}.temp", Path, Product.ProductId);
if (File.Exists(Source)) {
File.Delete(Source);
}
if (File.Exists(Temp)) {
File.Delete(Temp);
}
foreach (Img Img in Product.Imgs) {
string Cropped = String.Format("{0}/{1}.cropped", Path, Img.ImageId);
if (File.Exists(Cropped)) {
File.Delete(Cropped);
}
}
}
public void Resize( short[] Dimensions, string FileName, Image Image, EncoderParameters EncoderParameters) {
if (Dimensions[1] == 0) {
Dimensions[1] = (short)(Image.Height / ((float)Image.Width / (float)Dimensions[0]));
}
using (Bitmap Bitmap = new Bitmap(Dimensions[0], Dimensions[1])) {
using (Graphics Graphics = Graphics.FromImage(Bitmap)) {
Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
Graphics.SmoothingMode = SmoothingMode.HighQuality;
Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
Graphics.CompositingQuality = CompositingQuality.HighQuality;
Graphics.DrawImage(Image, 0, 0, Dimensions[0], Dimensions[1]);
};
Bitmap.Save(FileName, JpegCodecInfo, EncoderParameters);
}
}
}
Вот одно из изображений, которое это производит:
UPDATE
Итак, я сел и прочитал MSDN примерно через 2 часа после публикации, проверив код, который у меня был. Насколько я могу судить, я использую максимально возможную настройку качества при выполнении манипуляции.
В любом случае, я закончил тем, что немного очистил и оптимизировал код. Я подумал о необходимости исходного файла и решил удалить его, потому что он требовал от меня дополнительной работы, чтобы выяснить размеры обрезки, основанные на временном файле. Итак, оно ушло.
Кроме того, где-то вдоль обтекания исчезла черная линия, поэтому я могу только предположить, что проблемы с соотношением сторон должны были быть исправлены, как указано @StefanE.
Кроме того, как сказал @VinayC, ресайзер генерировал значение высоты 479 (которое я до сих пор не понимаю, как, но что угодно ...), но это, похоже, было исправлено, когда я переключился чтобы использовать классы System.Drawing.Size и System.Drawing.Rectangle на всем пути, вместо использования моих собственных классов, которые по сути делают то же самое.
Итак, вот обновленный код. Оставшиеся две ошибки ошибка все еще не устранена, поэтому у меня есть контур "поблескивания" вокруг изображения (см. Второе вложение), который я могу сузить до изменения размера, потому что оно отображается в первой размер, где обрезка не произошла. И вторая ошибка в том, что обрезанные версии всегда располагаются ниже по оси Y, чем то, что я передаю как обрезка. Я бы оценил, что это на 5% -8% ниже, чем то, что я говорю, поэтому не уверен насчет этого (позиции не должны меняться ... если я не передам плохое число из jQuery, надо проверить на этом ...).
(Моя вторая ошибка закончилась тем, что я сначала отправил неверные значения, div обрезки получал свое относительное положение на основе div основного содержимого, а не div контейнера изображений. Все исправлено.)
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Web;
namespace Website.Models.Providers {
public class ImageProvider {
private readonly ProductProvider ProductProvider = null;
private readonly EncoderParameters DefaultQualityEncoder = new EncoderParameters();
private readonly EncoderParameters HighQualityEncoder = new EncoderParameters();
private readonly ImageCodecInfo JpegCodecInfo = ImageCodecInfo.GetImageEncoders().Single(
c =>
(c.MimeType == "image/jpeg"));
private readonly Size[] Sizes = new Size[3] {
new Size(640, 0),
new Size(240, 0),
new Size(80, 0)
};
private readonly string Path = HttpContext.Current.Server.MapPath("~/Resources/Images/Products");
public ImageProvider(
ProductProvider ProductProvider) {
this.ProductProvider = ProductProvider;
this.DefaultQualityEncoder.Param[0] = new EncoderParameter(Encoder.Quality, 80L);
this.HighQualityEncoder.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
}
public void Crop(
string FileName,
Image Image,
Crop Crop) {
using (Bitmap Source = new Bitmap(Image)) {
using (Bitmap Target = new Bitmap(Crop.Width, Crop.Height)) {
using (Graphics Graphics = Graphics.FromImage(Target)) {
Graphics.CompositingMode = CompositingMode.SourceCopy;
Graphics.CompositingQuality = CompositingQuality.HighQuality;
Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
Graphics.SmoothingMode = SmoothingMode.HighQuality;
Graphics.DrawImage(Source, new Rectangle(0, 0, Target.Width, Target.Height), new Rectangle(Crop.Left, Crop.Top, Crop.Width, Crop.Height), GraphicsUnit.Pixel);
};
Target.Save(FileName, JpegCodecInfo, HighQualityEncoder);
};
};
}
public void CropAndResize(
Product Product,
Crop Crop) {
using (Image Temp = Image.FromFile(String.Format("{0}/{1}.temp", Path, Product.ProductId))) {
Img Img = new Img();
this.ProductProvider.AddImageAndSave(Product, Img);
this.Crop(String.Format("{0}/{1}.cropped", Path, Img.ImageId), Temp, Crop);
using (Image Cropped = Image.FromFile(String.Format("{0}/{1}.cropped", Path, Img.ImageId))) {
this.Resize(String.Format("{0}/{1}-L.jpg", Path, Img.ImageId), this.Sizes[0], Cropped, HighQualityEncoder);
this.Resize(String.Format("{0}/{1}-T.jpg", Path, Img.ImageId), this.Sizes[1], Cropped, HighQualityEncoder);
this.Resize(String.Format("{0}/{1}-S.jpg", Path, Img.ImageId), this.Sizes[2], Cropped, HighQualityEncoder);
};
};
this.Purge(Product);
}
public void QueueFor(
Product Product,
Size Size,
HttpPostedFileBase PostedFile) {
using (Image Image = Image.FromStream(PostedFile.InputStream)) {
this.Resize(String.Format("{0}/{1}.temp", Path, Product.ProductId), Size, Image, HighQualityEncoder);
};
}
private void Purge(
Product Product) {
string Temp = String.Format("{0}/{1}.temp", Path, Product.ProductId);
if (File.Exists(Temp)) {
File.Delete(Temp);
};
foreach (Img Img in Product.Imgs) {
string Cropped = String.Format("{0}/{1}.cropped", Path, Img.ImageId);
if (File.Exists(Cropped)) {
File.Delete(Cropped);
};
};
}
public void Resize(
string FileName,
Size Size,
Image Image,
EncoderParameters EncoderParameters) {
if (Size.Height == 0) {
Size.Height = (int)(Image.Height / ((float)Image.Width / (float)Size.Width));
};
using (Bitmap Bitmap = new Bitmap(Size.Width, Size.Height)) {
using (Graphics Graphics = Graphics.FromImage(Bitmap)) {
Graphics.CompositingMode = CompositingMode.SourceCopy;
Graphics.CompositingQuality = CompositingQuality.HighQuality;
Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
Graphics.SmoothingMode = SmoothingMode.HighQuality;
Graphics.DrawImage(Image, new Rectangle(0, 0, Size.Width, Size.Height));
};
Bitmap.Save(FileName, JpegCodecInfo, EncoderParameters);
};
}
}
}
На этом рисунке показан контур "ореола", на который указывают красные стрелки.