Изменить размер изображения, чтобы поместиться в ограничивающий прямоугольник
Легкая проблема, но по какой-то причине я просто не могу понять это сегодня.
Мне нужно изменить размер изображения до максимально возможного размера, который поместится в ограничительной рамке при сохранении соотношения сторон.
В основном я ищу код для заполнения этой функции:
void CalcNewDimensions(ref int w, ref int h, int MaxWidth, int MaxHeight);
Где w & h - исходная высота и ширина (в), а новые высота и ширина (в), а MaxWidth и MaxHeight определяют ограничивающий прямоугольник, в который должно вписываться изображение.
9 ответов
Найдите что меньше: MaxWidth / w
или же MaxHeight / h
Затем умножить w
а также h
по этому номеру
Объяснение:
Вам нужно найти коэффициент масштабирования, который делает изображение подходящим.
Чтобы найти коэффициент масштабирования, s
для ширины, то s
должен быть таким, чтобы: s * w = MaxWidth
, Следовательно, коэффициент масштабирования MaxWidth / w
,
Аналогично для высоты.
Тот, который требует наибольшего масштабирования (меньше s
) является фактором, с помощью которого вы должны масштабировать все изображение.
По предложению Эрика я бы сделал что-то вроде этого:
private static Size ExpandToBound(Size image, Size boundingBox)
{
double widthScale = 0, heightScale = 0;
if (image.Width != 0)
widthScale = (double)boundingBox.Width / (double)image.Width;
if (image.Height != 0)
heightScale = (double)boundingBox.Height / (double)image.Height;
double scale = Math.Min(widthScale, heightScale);
Size result = new Size((int)(image.Width * scale),
(int)(image.Height * scale));
return result;
}
Возможно, я немного забросил заброски, но я просто пытался сохранить точность вычислений.
Чтобы выполнить заливку аспекта вместо подгонки аспекта, используйте вместо этого большее соотношение. То есть измените код Мэтта с Math.Min на Math.Max.
(Заполнение аспекта не оставляет пустую ограничивающую рамку, но может поместить часть изображения за пределы границ, в то время как подбор аспекта не оставляет изображение за пределами границ, но может оставить пустую часть ограничительной рамки.)
Пробовал код мистера Уоррена, но он не дал надежных результатов.
Например,
ExpandToBound(new Size(640,480), new Size(66, 999)).Dump();
// {Width=66, Height=49}
ExpandToBound(new Size(640,480), new Size(999,50)).Dump();
// {Width=66, Height=50}
Вы можете видеть, высота = 49 и высота = 50 в другом.
Вот мой (основанная версия кода мистера Уоррена) без расхождений и небольшого рефакторинга:
// Passing null for either maxWidth or maxHeight maintains aspect ratio while
// the other non-null parameter is guaranteed to be constrained to
// its maximum value.
//
// Example: maxHeight = 50, maxWidth = null
// Constrain the height to a maximum value of 50, respecting the aspect
// ratio, to any width.
//
// Example: maxHeight = 100, maxWidth = 90
// Constrain the height to a maximum of 100 and width to a maximum of 90
// whichever comes first.
//
private static Size ScaleSize( Size from, int? maxWidth, int? maxHeight )
{
if ( !maxWidth.HasValue && !maxHeight.HasValue ) throw new ArgumentException( "At least one scale factor (toWidth or toHeight) must not be null." );
if ( from.Height == 0 || from.Width == 0 ) throw new ArgumentException( "Cannot scale size from zero." );
double? widthScale = null;
double? heightScale = null;
if ( maxWidth.HasValue )
{
widthScale = maxWidth.Value / (double)from.Width;
}
if ( maxHeight.HasValue )
{
heightScale = maxHeight.Value / (double)from.Height;
}
double scale = Math.Min( (double)(widthScale ?? heightScale),
(double)(heightScale ?? widthScale) );
return new Size( (int)Math.Floor( from.Width * scale ), (int)Math.Ceiling( from.Height * scale ) );
}
Следующий код дает более точные результаты:
public static Size CalculateResizeToFit(Size imageSize, Size boxSize)
{
// TODO: Check for arguments (for null and <=0)
var widthScale = boxSize.Width / (double)imageSize.Width;
var heightScale = boxSize.Height / (double)imageSize.Height;
var scale = Math.Min(widthScale, heightScale);
return new Size(
(int)Math.Round((imageSize.Width * scale)),
(int)Math.Round((imageSize.Height * scale))
);
}
Очень просто.:) Проблема в том, чтобы найти фактор, на который нужно умножить ширину и высоту. Решение состоит в том, чтобы попытаться использовать один, и если он не подходит, используйте другой. Так...
private float ScaleFactor(Rectangle outer, Rectangle inner)
{
float factor = (float)outer.Height / (float)inner.Height;
if ((float)inner.Width * factor > outer.Width) // Switch!
factor = (float)outer.Width / (float)inner.Width;
return factor;
}
Чтобы подогнать картинку (pctRect) к окну (wndRect), вызовите это
float factor=ScaleFactor(wndRect, pctRect); // Outer, inner
RectangleF resultRect=new RectangleF(0,0,pctRect.Width*factor,pctRect.Height*Factor)
Код Python, но, возможно, он укажет вам правильное направление:
def fit_within_box(box_width, box_height, width, height):
"""
Returns a tuple (new_width, new_height) which has the property
that it fits within box_width and box_height and has (close to)
the same aspect ratio as the original size
"""
new_width, new_height = width, height
aspect_ratio = float(width) / float(height)
if new_width > box_width:
new_width = box_width
new_height = int(new_width / aspect_ratio)
if new_height > box_height:
new_height = box_height
new_width = int(new_height * aspect_ratio)
return (new_width, new_height)
Основываясь на предыдущих ответах, вот функция Javascript:
/**
* fitInBox
* Constrains a box (width x height) to fit in a containing box (maxWidth x maxHeight), preserving the aspect ratio
* @param width width of the box to be resized
* @param height height of the box to be resized
* @param maxWidth width of the containing box
* @param maxHeight height of the containing box
* @param expandable (Bool) if output size is bigger than input size, output is left unchanged (false) or expanded (true)
* @return {width, height} of the resized box
*/
function fitInBox(width, height, maxWidth, maxHeight, expandable) {
"use strict";
var aspect = width / height,
initWidth = width,
initHeight = height;
if (width > maxWidth || height < maxHeight) {
width = maxWidth;
height = Math.floor(width / aspect);
}
if (height > maxHeight || width < maxWidth) {
height = maxHeight;
width = Math.floor(height * aspect);
}
if (!!expandable === false && (width >= initWidth || height >= initHeight)) {
width = initWidth;
height = initHeight;
}
return {
width: width,
height: height
};
}
Код Python для этой задачи основан на ответе Джейсона с исправлением для масштабирования и переупорядочения аргументов для обычной передачи аргументов с помощью img.shape.
def fit_within_box(box_height, box_width, height, width):
"""
Returns a tuple (new_width, new_height) which has the property
that it fits within box_width and box_height and has (close to)
the same aspect ratio as the original size
"""
new_width, new_height = width, height
aspect_ratio = float(width) / float(height)
if new_width > box_width or new_height < box_height:
new_width = box_width
new_height = int(new_width / aspect_ratio)
if new_height > box_height or new_width < box_width:
new_height = box_height
new_width = int(new_height * aspect_ratio)
return new_height, new_width
У меня была похожая проблема, и я нашел это очень полезным: статья. Как я правильно понял, вам нужно изменить размер изображения?