09 июня 2020

Изменение разрешения изображения

    Предположим, что у вас возникла нужда изменить разрешение изображения. Например, чтобы сделать его миниатюру (thumbnail) для предварительного просмотра. Для этого можно воспользоваться какой-нибудь библиотекой для работы с графикой. Можно реализовать алгоритм самому. Но можно обойтись и подручными средствами, то есть функциями Delphi и WinAPI.
    Классической реализацией масштабирования изображения, которую вы можете найти в различных FAQ и на форумах является примерно вот такая реализация:
procedure ScaleJpeg1(jpg: TJPEGImage; const NewWidth, NewHeight: Integer);
var
  bmp: TBitmap;
  fScale: Double;
begin
  fScale := Min(NewWidth / jpg.Width, NewHeight / jpg.Height);
  bmp := TBitmap.Create;
  try
    bmp.SetSize(Round(jpg.Width * fScale), Round(jpg.Height * fScale));
    bmp.Canvas.StretchDraw(bmp.Canvas.ClipRect, jpg);
    jpg.Assign(bmp);
  finally
    bmp.Free;
  end;
end;
Это работает. Но если хорошо присмотреться или увеличить полученное изображение, то по краям предмета можно увидеть "зубчики", хотя на исходном изображении их нет:


Кого-то это устроит, а кого-то - нет. Причина этих зубчиков нашлась в методе Vcl.Graphics.TBitmap.Draw. В том, с какими параметрами он вызывает из WinAPI функцию SetStretchBltMode.
    Функция SetStretchBltMode устанавливает режим масштабирования изображения в заданном контексте устройства. В нашем случае устройство - это canvas, на котором будем рисовать изображение нового масштаба.
int SetStretchBltMode
  (
    HDC hdc,
    int mode
  );
где
  • hdc – хэндл устройства (Canvas.Handle) на котором будем рисовать рисунок;
  • mode - режим масштабирования.
Режим масштабирования изображения может принимать следующие значения:
  • BLACKONWHITE - выполняется булева операция AND используя коды цвета для ликвидируемых и существующих пикселей (если растровое изображение является монохромны, то этот режиме сохраняет черные пиксели за счет белых пикселей);
  • WHITEONBLACK - выполняется булева операция OR используя коды цвета для ликвидируемых и существующих пикселей (если растровое изображение является монохромны, то этот режим сохраняет белые пиксели за счет черных пикселей);
  • COLORONCOLOR - удаляет все ликвидируемые строки пикселей, не пытаясь сберечь их информацию
  • HALFTONE - преобразует пиксели исходного прямоугольника в блоки пикселей в целевом прямоугольнике и среднее значение цвета всего целевого блока пикселей подбирается близким по значению к цвету исходных пикселей;
  • STRETCH_ANDSCANS - равнозначен BLACKONWHITE (Windows 95/98);
  • STRETCH_ORSCANS - равнозначен WHITEONBLACK (Windows 95/98);
  • STRETCH_DELETESCANS - равнозначен COLORONCOLOR (Windows 95/98);
  • STRETCH_HALFTONE - равнозначен HALFTONE (Windows 95/98).
Режимы масштабирования изображения BLACKONWHITE (STRETCH_ANDSCANS) и WHITEONBLACK (STRETCH_ORSCANS) обычно используются для масштабирования монохромных изображений, а режим COLORONCOLOR (STRETCH_DELETESCANS) для цветных. Если мы хотим улучшить качество нашего результирующего изображения, то нам нужно выбрать режим HALFTONE. Но учтите, что он значительно медленнее чем остальные режимы, т.к. требует большей обработки исходного изображения.
    В большинстве случаев метод Graphics.TBitmap.Draw использует режим COLORONCOLOR. Поэтому, если мы хотим улучшить качество изображения, то нам с ним не по пути. Давайте нарисуем изображение с новым масштабом сами.
procedure ScaleJpeg2(jpg: TJPEGImage; const NewWidth, NewHeight: Integer; 
                     const StretchMode: Integer = HALFTONE);
var
  bmp: Vcl.Graphics.TBitmap;
  fScale: Double;
  PrevPt: TPoint;
begin
  fScale := Min(NewWidth / jpg.Width, NewHeight / jpg.Height);
  bmp := Vcl.Graphics.TBitmap.Create;
  try
    bmp.SetSize(Round(jpg.Width * fScale), Round(jpg.Height * fScale));
    GetBrushOrgEx(bmp.Canvas.Handle, PrevPt);
    SetStretchBltMode(bmp.Canvas.Handle, StretchMode);
    SetBrushOrgEx(bmp.Canvas.Handle, PrevPt.x, PrevPt.y, @PrevPt);
    StretchBlt(bmp.Canvas.Handle, 0, 0, bmp.Width, bmp.Height, 
               jpg.Canvas.Handle, 0, 0, jpg.Width, jpg.Height, 
               SRCCOPY);
    jpg.Assign(bmp);
  finally
    bmp.Free;
  end;
end;
Согласно документации, после установки режима HALFTONE, чтобы избежать смещения кисти при ее автоматической корректировке операционной системой, необходимо вызвать функцию SetBrushOrgEx. Поэтому в мою процедуру добавлен вызов GetBrushOrgEx и SetBrushOrgEx (строки 12 и 14).
    При вызове процедуры ScaleJpeg2 с параметром StretchMode=COLORONCOLOR мы получим изображение идентичное результату работы ScaleJpeg1. Но если вызвать ее с параметром StretchMode=HALFTONE, то зубчики сгладятся и станут еле заметными:


    Таким образом мы имеем функцию изменения разрешения изображения, которая позволяет нам выбирать, в зависимости от поставленной задачи, между скоростью обработки изображения и качеством ее результатов.

    P.S. Я отвлекся на релиз Delphi 10.4 Sydney, поэтому между датой написания этой статьи и датой ее публикации прошло больше трех недель. За это время, в результате моих тестов, всплыл один интересный факт, под влиянием которого в функцию ScaleJpeg2 мне пришлось внести небольшие изменения, которые привели к значительному повышению скорости ее работы, и послужили поводом к написанию продолжения этой статьи. Которое так же будет примером практического применения информации из статьи Доступ к private членам класса.

Комментариев нет:

Отправка комментария