Контраст и резкость в Direct2D представлены эффектами Contrast и Sharpen соответственно. Как обычно, Microsoft опять накосячил с математикой в документации. Эта неприятность будет исправлена ниже.
Контраст
Эффект Contrast увеличивает или уменьшает контрастность изображения. Возникает закономерный вопрос, что такое контрастность. Это такой же субъективный параметр, как и «яркость». Существует только в рамках всего изображения. Нельзя взять один цвет и заявить, что он очень контрастный.
Иоханнес Иттен в «Искусстве цвета» выделял 7 видов контраста. В нашем случае актуален второй тип: Контраст светлого и темного.
Эффект Contrast
Эффект контраста доступен только с версии Windows 10. Никакие ухищрения не позволят поставить DirectX 12 на другие версии Windows. Такова политика Microsoft.
Снова подключаем модуль Winapi.D2DMissing из библиотеки SVGIconImageList. Напомню, это из-за «бедности» Delphi на заголовки D2D. В этом модуле содержится описание для интерфейсов: ID2D1DeviceContext, ID2D1Image и ID2D1Effect. Остальное по-прежнему ищем и дописываем руками.
CLSID для эффекта Contrast:
1 2 |
const CLSID_D2D1Contrast: TGUID = '{b648a78a-0ed5-4f80-a94a-8e825aca6b77}'; |
Эффекту контраста требуются установить два свойства:
1 2 3 4 5 6 7 8 9 10 11 12 |
type // The enumeration of the Contrast effect's top level properties. // Effect description: Adjusts the contrast of an image. TD2D1_CONTRAST_PROP = ( // Property Name: "Contrast" // Property Type: FLOAT D2D1_CONTRAST_PROP_CONTRAST = 0, // Property Name: "ClampInput" // Property Type: BOOL D2D1_CONTRAST_PROP_CLAMP_INPUT = 1, D2D1_CONTRAST_PROP_FORCE_DWORD = $ffffffff ); |
D2D1_CONTRAST_PROP_CONTRAST Значение с плавающей запятой. Указывает величину контрастности изображения. Отрицательные значения уменьшают контраст, положительные — увеличивают. Минимальное значение: -1.0, максимальное: 1.0. Значение по умолчанию для свойства: 0,0. |
D2D1_CONTRAST_PROP_CLAMP_INPUT Логическое значение, указывающее, ограничивать ли ввод до [0.0, 1.0]. Значение по умолчанию для свойства — FALSE. |
Если честно, по второму параметру особого эффекта от применения не заметил.
Функция создания эффекта Contrast выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function GetContrastEffect(AContext: ID2D1DeviceContext; AImage: ID2D1Image; const AContrast: Single; const AClampInput: Boolean; out AEffect: ID2D1Effect): Boolean; var v: UInt32; hr: HRESULT; begin Result := Assigned(AContext) and Succeeded(AContext.CreateEffect( CLSID_D2D1Contrast, AEffect)); if not Result then Exit; AEffect.SetInput(0, AImage); hr := AEffect.SetValue(Cardinal(D2D1_CONTRAST_PROP_CONTRAST), D2D1_PROPERTY_TYPE_FLOAT, @AContrast, SizeOf(AContrast)); Result := Result and Succeeded(hr); v := Abs(Integer(AClampInput)); hr := AEffect.SetValue(Cardinal(D2D1_CONTRAST_PROP_CLAMP_INPUT), D2D1_PROPERTY_TYPE_BOOL, @v, SizeOf(v)); Result := Result and Succeeded(hr); end; |
Математика эффекта Contrast
Эффект контраста обрабатывает каждый цветовой канал, согласно уравнениям ниже. Уравнения представляют из себя два кусочно-квадратичных полинома, который встречаются в точке (0.5,0.5). В диаграмме по оси X — исходное значение канала, в диапазоне [0..1]. По оси Y — результат усиления или уменьшения контраста.
Ниже иллюстрация из статьи MSDN, посвященной эффекту контраста.
Применить математику
Попытка воспроизвести формулы никак не влияет на форму кривой в центре. На рисунке представлены результаты применения эффекта с максимальными и минимальными значениями контраста. Эффект виден, кривая — нет.
Воспользуемся методом из прошлой статьи. У нас уже есть шаблон с равномерным распределением цвета-яркости. Применим к нему эффект Contrast и прочитаем получившиеся значения.
Уже два эффекта желают получать данные именно так, через шаблон. Поэтому модифицируем код. Создание WicImage, получение кривой и прочие общие штуки вынесем в отдельную функцию.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
type TGetEffectProc = reference to procedure( AContext: ID2D1DeviceContext; AImage: ID2D1Image; out AEffect: ID2D1Effect); var GCanvas: TDirect2DCanvas = nil; function GetRealCurve(const ABitmap: TBitmap; var AData: array of TPointF; const AGetEffectProc: TGetEffectProc ): Integer; var rct: TRect; bmp: TBitmap; effect: ID2D1Effect; context: ID2D1DeviceContext; bitmap: ID2D1Bitmap; WicImage: TWicImage; i,cnt: Integer; sp: PRGBQuad; dp: PRGBQuad; pnt: TPointF; begin Result := Length(Adata); ZeroMemory(@AData, Result*SizeOf(TPointF)); rct := ABitmap.Canvas.ClipRect; bmp := CreateBmpRect(rct, pf32bit); if not (Assigned(GCanvas)) then GCanvas := TDirect2DCanvas.Create( bmp.Canvas.Handle, bmp.Canvas.ClipRect); WicImage := TWicImage.Create; try WicImage.Assign(ABitmap); GCanvas.RenderTarget.CreateBitmapFromWicBitmap( WICImage.Handle, nil, Bitmap); (GCanvas.RenderTarget as ID2D1DCRenderTarget).BindDC( bmp.Canvas.Handle, bmp.Canvas.ClipRect); GCanvas.RenderTarget.QueryInterface(ID2D1DeviceContext, context); if Assigned(context) and (assigned(bitmap)) and Assigned(AGetEffectProc) then AGetEffectProc(context, bitmap as ID2D1Image, effect); GCanvas.BeginDraw; try if Assigned(effect) then context.DrawImage(effect as ID2D1Image); finally GCanvas.EndDraw; end; sp := (ABitmap.ScanLine[ABitmap.Height-1]); dp := (bmp.ScanLine[bmp.Height-1]); cnt := bmp.Width; for i := 0 to cnt - 1 do begin pnt.X := sp^.rgbBlue/255; pnt.Y := dp^.rgbBlue/255; AData[Trunc(pnt.X * Result)] := pnt; inc(sp); inc(dp); end; pnt := AData[0]; for i := 1 to Result - 1 do begin if (AData[i].X=0) and (AData[i].Y=0) then AData[i] := pnt else pnt := AData[i]; end; finally FreeAndNil(WicImage); FreeAndNil(bmp); end; end; |
Почти такая же функция, как для получения значений яркости. Однако, само создание конкретного эффекта обернуто процедурой.
Таким образом, получение кривой для контраста выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function GetRealContrastCurve(const AContrast: Single; const AClampInput: Boolean; const ABitmap: TBitmap; var AData: array of TPointF): Integer; begin Result := GetRealCurve(ABitmap, AData, procedure( AContext: ID2D1DeviceContext; AImage: ID2D1Image; out AEffect: ID2D1Effect) begin GetContrastEffect(AContext, AImage, AContrast, AClampInput, AEffect); end ); end; |
После обработки шаблона при максимальном контрасте получили кривую, один в один, как в статье MSDN на рис.1. Поэтому, разбираемся и убираем сомнительные места из математики эффекта.
Исправление косяков математики
Чтобы сильно не умничать сразу укажу проблемные места.
Все рассуждения в статье ведутся в диапазоне [0..1] для каналов и [-1..1] для контраста. Однако конкретно в этом случае человек решил, что контраст изменяется в диапазоне [-100..100]. Поэтому дополнительно разделил на 100. Это не верно, а правильно так:
И второй момент:
Представленные уравнения не являются квадратичными полиномами. Вот если убрать последнее умножение и оставить только коэффициент С, они станут полиномами:
Таким образом, функция получения кривой контраста выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
function GetContrastHalfCurve(const AContrast: Single; const ATopHalf: Boolean; var AData: Array of TPointF): Integer; var i: Integer; pnt: TPointF; s,a,b,c,d: Single; begin Result := Length(AData); s := 1 - (3*AContrast)/4; if ATopHalf then begin a := 2*(s - 1); b := 4 - 3*s; c := s - 1; d := 0.5; end else begin a := 2*(1 - s); b := s; c := 0; d := 0; end; for i := 0 to Result - 1 do begin pnt.X := d + (i/(2*Result)); pnt.Y := a*(pnt.X*pnt.X) + b*pnt.X + c; AData[i] := pnt; end; end; |
И наша старая знакомая, имею ввиду несостоявшуюся кривую, теперь выглядит так:
Резкость
Резкость описывает различимость деталей на фотографии, и она может использоваться как важный творческий инструмент для выделения текстуры.
Cambridge in Colour
В Direct2D резкость представлена эффектом Sharpen. Эффект только повышает резкость. Понижением резкости, то есть размытием, занимается Gaussian blur эффект, про который говорилось ранее.
Эффект Sharpen
Эффект контраста, также как и эффект резкости, доступен только начиная с версии Windows 10.
По тем же причинам нужен модуль Winapi.D2DMissing.
CLSID для эффекта Contrast:
1 2 |
const CLSID_D2D1Sharpen: TGUID = '{C9B887CB-C5FF-4DC5-9779-273DCF417C7D}'; |
Свойства эффекта:
1 2 3 4 5 6 7 8 9 10 11 12 |
type // The enumeration of the Sharpen effect's top level properties. // Effect description: Performs sharpening adjustment TD2D1_SHARPEN_PROP = ( // Property Name: "Sharpness" // Property Type: FLOAT D2D1_SHARPEN_PROP_SHARPNESS = 0, // Property Name: "Threshold" // Property Type: FLOAT D2D1_SHARPEN_PROP_THRESHOLD = 1, D2D1_SHARPEN_PROP_FORCE_DWORD = $ffffffff ); |
D2D1_SHARPEN_PROP_SHARPNESS Значение с плавающей запятой. Определяет насколько повысить резкость входного изображения. Допустимый диапазон от 0.0 до 10.0 Значение по умолчанию: 0.0 |
D2D1_SHARPEN_PROP_THRESHOLD Значение с плавающей запятой. Определяет минимальный перепад яркости между соседними областями, чтобы граница между ними считалась контуром. Чем ниже порог, тем более незаметные детали будут подвергаться эффекту резкости. Допустимый диапазон от 0.0 до 1.0 Значение по умолчанию: 0.0 |
Функция создания эффекта Sharpen выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function GetSharpenEffect(AContext: ID2D1DeviceContext; AImage: ID2D1Image; const ASharpness, AThreshold: Single; out AEffect: ID2D1Effect): Boolean; var hr: HRESULT; begin Result := Assigned(AContext) and Succeeded(AContext.CreateEffect( CLSID_D2D1Sharpen, AEffect)); if not Result then Exit; AEffect.SetInput(0, AImage); hr := AEffect.SetValue(Cardinal(D2D1_SHARPEN_PROP_SHARPNESS), D2D1_PROPERTY_TYPE_FLOAT, @ASharpness, SizeOf(ASharpness)); Result := Result and Succeeded(hr); hr := AEffect.SetValue(Cardinal(D2D1_SHARPEN_PROP_THRESHOLD), D2D1_PROPERTY_TYPE_FLOAT, @AThreshold, SizeOf(AThreshold)); Result := Result and Succeeded(hr); end; |
Примеры
Скачать
Исходники (Delphi XE 7-10) 2.84 Mб
Исполняемый файл D2DEffects 0.2 (zip) 1.78 Мб
Видны некоторые изменения по демо-программе, что радует ) Впрочем, как и сама статья. Жду продолжения цикла, читается как детектив с математическим расследованием: конечно же, благодаря ошибкам в документации MS =)
Спасибо! Да уж, MS подбрасывает загадок.
Исчезну на некоторое время, что-то тут прилетело интересное. Продолжение детектива будет обязательно )))