BSOne.SFC/EM.Lib/ImageEn_SRC/Source/histogrambox.pas

1303 lines
35 KiB
Plaintext

(* ImageEn Build 7.0.0.06.2637 @ 7-4-17 14:58:42.679 *)
(*
Copyright (c) 1998-2017 by Carlotta Calandra. All rights reserved.
Copyright (c) 2011-2017 by Xequte Software.
This software comes without express or implied warranty.
In no case shall the author be liable for any damage or unwanted behavior of any
computer hardware and/or software.
Author grants you the right to include the component
in your application, whether COMMERCIAL, SHAREWARE, or FREEWARE.
ImageEn, IEvolution and ImageEn ActiveX may not be included in any
commercial, shareware or freeware libraries or components.
www.ImageEn.com
*)
(*
File version 1007
Doc revision 1001
*)
unit histogrambox;
{$R-}
{$Q-}
{$I ie.inc}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, ImageEnProc, hyiedefs, iexBitmaps;
type
{!!
<FS>THistogramKind
<FM>Declaration<FC>
}
THistogramKind = set of (hkRed, hkGreen, hkBlue, hkGray, hkHue);
{!!}
{!!
<FS>THistogramLabels
<FM>Declaration<FC>
}
THistogramLabels = set of (hlVertical, hlHorizontal);
{!!}
{!!
<FS>THistogramStyle
<FM>Declaration<FC>
}
THistogramStyle = (hsBars, hsLines, hsFilledLines);
{!!}
{!!
<FS>THistogramMouseInteract
<FM>Declaration<FC>
THistogramMouseInteract = (mhNone, mhSelectValue, mhSelectRange);
<FM>Description<FN>
What action does the mouse perform:
<TABLE>
<R> <H>Value</H> <H>Description</H> </R>
<R> <C>mhNone</C> <C>Mouse has no effect</C> </R>
<R> <C>mhSelectValue</C> <C>Clicking/moving the mouse selects a single value on the histogram</C> </R>
<R> <C>mhSelectRange</C> <C>Clicking/moving the mouse selects a range of values on the histogram</C> </R>
</TABLE>
!!}
THistogramMouseInteract = (mhNone, mhSelectValue, mhSelectRange);
{!!
<FS>THistogramBox
<FM>Description<FN>
THistogramBox can be attached to a <A TImageEnProc> component from which it gets information to compute and display the color channels histogram.
<FM>Demo<FN>
<TABLE2>
<R> <C_IMG_DEMO> <C>Demos\ImageAnalysis\Histogram\Histogram.dpr </C> </R>
</TABLE>
<FM>Example<FC>
// Setup
with HistogramBox1 do
begin
Background := clBtnFace; // Change background color
Font.Color := clDarkGray; // Set font and chart color to dark gray
MouseInteract := mhSelectValue; // Allow selection of a single value
HistogramKind := [ hkRed, hkGreen, hkBlue ]; // Histogram to show RGB levels
HistogramStyle := hsFilledLines; // Show a filled line graph instead of bars
end;
// Display histogram of colors in current image
HistogramBox1.UpdateFromBitmap( ImageEnView1.IEBitmap );
<FM>Methods and Properties<FN>
<TABLE2>
<R> <C_IMG_PUBLISHED> <C><A THistogramBox.AttachedImageEnProc></C> </R>
<R> <C_IMG_PUBLISHED> <C><A THistogramBox.Background></C> </R>
<R> <C_IMG_PROPERTY> <C><A THistogramBox.Bitmap></C> </R>
<R> <C_IMG_METHOD> <C><A THistogramBox.Clear></C> </R>
<R> <C_IMG_PUBLISHED> <C><A THistogramBox.CompBar></C> </R>
<R> <C_IMG_PUBLISHED> <C><A THistogramBox.GrayColor></C> </R>
<R> <C_IMG_PUBLISHED> <C><A THistogramBox.HistogramKind></C> </R>
<R> <C_IMG_PUBLISHED> <C><A THistogramBox.HistogramStyle></C> </R>
<R> <C_IMG_PROPERTY> <C><A THistogramBox.HistogramAbsXPos></C> </R>
<R> <C_IMG_PUBLISHED> <C><A THistogramBox.Labels></C> </R>
<R> <C_IMG_PUBLISHED> <C><A THistogramBox.MouseInteract></C> </R>
<R> <C_IMG_PROPERTY> <C><A THistogramBox.MinSelected></C> </R>
<R> <C_IMG_PROPERTY> <C><A THistogramBox.MaxSelected></C> </R>
<R> <C_IMG_PROPERTY> <C><A THistogramBox.Scale></C> </R>
<R> <C_IMG_PROPERTY> <C><A THistogramBox.SelectionColor></C> </R>
<R> <C_IMG_METHOD> <C><A THistogramBox.UpdateFromBitmap></C> </R>
<R> <C_IMG_METHOD> <C><A THistogramBox.UpdateFromHistogram></C> </R>
</TABLE>
<FM>Events<FN>
<TABLE2>
<R> <C_IMG_EVENT> <C><A THistogramBox.OnSelectionChanged></C> </R>
<R> <C_IMG_EVENT> <C><A THistogramBox.OnSelectionChanging></C> </R>
</TABLE>
!!}
{$ifdef IEHASPLATFORMATTRIBUTE}
[ComponentPlatformsAttribute(pidWin32 or pidWin64)]
{$endif}
THistogramBox = class(TCustomControl)
private
{ Private declarations }
fBackground: TColor;
fAIEP: TImageEnProc; // attached TImageEnProc
fHistKind: THistogramKind;
fLabels: THistogramLabels;
fCompBar: boolean;
fHistogramStyle: THistogramStyle;
fHistogramAbsXPos: integer;
fGrayColor: TColor;
fMouseMinX : Integer;
fMouseMaxX : Integer;
fMouseMinDown : Boolean;
fMouseMaxDown : Boolean;
fMouseInteract: THistogramMouseInteract;
fSelectionColor : TColor;
fOnSelectionChanged: TNotifyEvent;
fOnSelectionChanging: TNotifyEvent;
fScale: THistogramScale;
procedure SetBackground(bk: TColor);
procedure SetLabels(v: THistogramLabels);
procedure SetCompBar(v: boolean);
procedure SetHistogramStyle(v: THistogramStyle);
procedure SetHistogramKind(v: THistogramKind);
procedure SetAIEP(v: TImageEnProc);
function GetHistogramKind: THistogramKind;
function GetLabels: THistogramLabels;
procedure SetMouseInteract(v: THistogramMouseInteract);
procedure SetSelectionColor(const Value: TColor);
function GetMaxSelected: Integer;
function GetMinSelected: Integer;
procedure SetMaxSelected(const Value: Integer);
procedure SetMinSelected(const Value: Integer);
procedure SetScale(Value: THistogramScale);
protected
{ Protected declarations }
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure WMEraseBkgnd(var Message: TMessage); message WM_ERASEBKGND;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
{ Public declarations }
{!!
<FS>THistogramBox.Bitmap
<FM>Declaration<FC>
property Bitmap: TBitmap;
<FM>Description<FN>
The bitmap of the current histogram
<FM>Example<FC>
uses
iexHelperFunctions;
// Print the current histogram
Printer.BeginDoc;
HistogramBox1.Bitmap.PrintImage(Printer.Canvas, 0, 0, 0, 0, ievpCenter, iehpCenter, iesFitToPage);
Printer.EndDoc;
!!}
Bitmap: TBitmap;
procedure Paint; override;
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
procedure Update(); override;
procedure UpdateFromBitmap(Bitmap: TIEBitmap);
procedure UpdateFromHistogram(Histogram: TIEHistogram);
procedure Clear();
{!!
<FS>THistogramBox.HistogramAbsXPos
<FM>Declaration<FC>
property HistogramAbsXPos: integer;
<FM>Description<FN>
Returns the left X coordinate where the histogram will be painted inside the component client area.
Read-only
!!}
property HistogramAbsXPos: integer read fHistogramAbsXPos;
property SelectionColor : TColor read fSelectionColor write SetSelectionColor;
property MinSelected : Integer read GetMinSelected write SetMinSelected;
property MaxSelected : Integer read GetMaxSelected write SetMaxSelected;
property Scale: THistogramScale read fScale write SetScale;
published
{ Published declarations }
property AttachedImageEnProc: TImageEnProc read fAIEP write SetAIEP;
property Background: TColor read fBackground write SetBackground default clWhite;
property HistogramKind: THistogramKind read GetHistogramKind write SetHistogramKind default [hkGray];
property Labels: THistogramLabels read GetLabels write SetLabels default [hlVertical, hlHorizontal];
property CompBar: boolean read fCompBar write SetCompBar default true;
property HistogramStyle: THistogramStyle read fHistogramStyle write SetHistogramStyle default hsBars;
{!!
<FS>THistogramBox.GrayColor
<FM>Declaration<FC>
property GrayColor: Tcolor;
<FM>Description<FN>
Specifies the color of the line/bar for a gray histogram (i.e. when <A THistogramBox.HistogramKind> is [hkGray]).
!!}
property GrayColor: TColor read fGrayColor write fGrayColor default clBlack;
{!!
<FS>THistogramBox.OnSelectionChanged
<FM>Declaration<FC>
property OnSelectionChanged: TNotifyEvent;
<FM>Description<FN>
If <A THistogramBox.MouseInteract> has been enabled then this event will occur after the user has modified the selection.
Note: Unlike <A THistogramBox.OnSelectionChanged> this event only fires once per change (on mouse up)
!!}
property OnSelectionChanged: TNotifyEvent read fOnSelectionChanged write fOnSelectionChanged;
{!!
<FS>THistogramBox.OnSelectionChanging
<FM>Declaration<FC>
property OnSelectionChanging: TNotifyEvent;
<FM>Description<FN>
If <A THistogramBox.MouseInteract> has been enabled then this event will occur as the user modifies the selection.
Note: Unlike <A THistogramBox.OnSelectionChanged> this event will fire multiple times during a selection change (as user drags mouse)
!!}
property OnSelectionChanging: TNotifyEvent read fOnSelectionChanging write fOnSelectionChanging;
property MouseInteract: THistogramMouseInteract read fMouseInteract write SetMouseInteract default mhNone;
property Align;
property DragCursor;
property DragMode;
property Enabled;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property Visible;
property OnClick;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnStartDrag;
property Font;
property Width default 256;
property Height default 105;
property OnContextPopup;
end;
implementation
uses
iegdiplus, hyieutils, imageenview;
{$R-}
/////////////////////////////////////////////////////////////////////////////////////
constructor THistogramBox.Create(Owner: TComponent);
begin
inherited Create(Owner);
IEGDIPLoadLibrary();
ControlStyle := ControlStyle + [csOpaque, csAcceptsControls];
fBackground := clWhite;
fHistKind := [hkGray];
fLabels := [hlVertical, hlHorizontal];
fCompBar := true;
fHistogramStyle := hsBars;
fGrayColor := clBlack;
Height := 105;
Width := 256;
bitmap := TBitmap.create;
bitmap.PixelFormat := pf24bit;
bitmap.Width := 1;
bitmap.Height := 1;
fAIEP := nil;
fMouseInteract := mhNone;
fMouseMinX := 0;
fMouseMaxX := 0;
fMouseMinDown := False;
fMouseMaxDown := False;
fSelectionColor := clRed;
fScale := iehsLinear;
Update;
end;
/////////////////////////////////////////////////////////////////////////////////////
destructor THistogramBox.Destroy;
begin
FreeAndNil(bitmap);
IEGDIPUnLoadLibrary();
inherited;
end;
procedure THistogramBox.Paint;
begin
canvas.Draw(0, 0, bitmap);
if ( fMouseInteract = mhSelectValue ) and ( fMouseMinX >= fHistogramAbsXPos ) then
begin
Canvas.Pen.Color := fSelectionColor;
Canvas.MoveTo( fMouseMinX, 0 );
Canvas.LineTo( fMouseMinX, Height );
end;
if ( fMouseInteract = mhSelectRange ) and ( fMouseMinX >= fHistogramAbsXPos ) then
begin
if fMouseMinX > fMouseMaxX then
begin
IESwap( fMouseMinX, fMouseMaxX );
IESwap( fMouseMinDown, fMouseMaxDown );
end;
Canvas.Brush.Color := fSelectionColor;
Canvas.FrameRect( Rect( fMouseMinX, 0 , fMouseMaxX + 1, Height ));
end;
end;
procedure THistogramBox.WMEraseBkgnd(var Message: TMessage);
begin
Message.Result := 0;
end;
procedure THistogramBox.WMSize(var Message: TWMSize);
begin
inherited;
bitmap.Width := message.Width;
bitmap.Height := message.Height;
Update;
end;
{!!
<FS>THistogramBox.Background
<FM>Declaration<FC>
property Background: TColor;
<FM>Description<FN>
Specifies the background color.
Note: To change the color of the font and grid use Font.Color
!!}
procedure THistogramBox.SetBackground(bk: TColor);
begin
fBackground := bk;
update;
end;
procedure THistogramBox.MouseMove(Shift: TShiftState; X, Y: Integer);
const
Cursor_Margin = 3;
begin
inherited;
if fMouseInteract in [ mhSelectValue, mhSelectRange ] then
begin
if fMouseMinDown then
fMouseMinX := ilimit( X, fHistogramAbsXPos, Width - 1 );
if fMouseMaxDown then
fMouseMaxX := ilimit( X, fHistogramAbsXPos, Width - 1 );
if fMouseMinDown or fMouseMaxDown then
begin
Invalidate;
if assigned( fOnSelectionChanging ) then
fOnSelectionChanging( Self);
end;
if ( X >= fHistogramAbsXPos - Cursor_Margin div 2 ) and
(( Abs( X - fMouseMinX ) <= Cursor_Margin ) OR ( Abs( X - fMouseMaxX ) <= Cursor_Margin )) then
Cursor := crHSplit
else
Cursor := crDefault;
end;
end;
procedure THistogramBox.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
inherited;
if ( Button = mbLeft ) and ( fMouseInteract = mhSelectValue ) and ( X >= fHistogramAbsXPos ) then
begin
fMouseMinDown := True;
fMouseMaxDown := False;
fMouseMinX := X;
Invalidate;
end
else
if ( Button = mbLeft ) and ( fMouseInteract = mhSelectRange ) and ( X >= fHistogramAbsXPos ) then
begin
fMouseMinDown := False;
fMouseMaxDown := False;
// Over Min or Max item?
if Abs( X - fMouseMinX ) < Abs( X - fMouseMaxX ) then
begin
fMouseMinX := X;
if fMouseMaxX < fHistogramAbsXPos then
fMouseMaxX := fHistogramAbsXPos;
fMouseMinDown := True;
end
else
begin
if fMouseMinX < fHistogramAbsXPos then
fMouseMinX := fHistogramAbsXPos;
fMouseMaxX := X;
fMouseMaxDown := True;
end;
Invalidate;
end;
end;
procedure THistogramBox.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
inherited;
if fMouseMinDown or fMouseMaxDown then
begin
fMouseMinDown := False;
fMouseMaxDown := False;
Invalidate;
// Ensure it is called even if user clicks once in a new position
if assigned( fOnSelectionChanging ) then
fOnSelectionChanging( Self);
if assigned( fOnSelectionChanged ) then
fOnSelectionChanged( Self);
end;
end;
procedure THistogramBox.SetLabels(v: THistogramLabels);
begin
if v <> fLabels then
begin
fLabels := v;
Update;
end;
end;
{!!
<FS>THistogramBox.MouseInteract
<FM>Declaration<FC>
property MouseInteract: <A THistogramMouseInteract>;
<FM>Description<FN>
Determines what effect use of the mouse has on the component
<FM>Example<FC>
// Allow the user to select a value on the histogram
HistogramBox1.MouseInteract := mhSelectValue;
<FM>See Also<FC>
- <A THistogramBox.MinSelected>
- <A THistogramBox.MaxSelected>
- <A THistogramBox.SelectionColor>
!!}
procedure THistogramBox.SetMouseInteract(v: THistogramMouseInteract);
begin
if v <> fMouseInteract then
begin
fMouseInteract := v;
Invalidate;
end;
end;
{!!
<FS>THistogramBox.SelectionColor
<FM>Declaration<FC>
property SelectionColor: TColor;
<FM>Description<FN>
Specifies the color of the selection line/box if you have enabled mouse interaction.
<FM>See Also<FC>
- <A THistogramBox.MouseInteract>
!!}
procedure THistogramBox.SetSelectionColor(const Value: TColor);
begin
fSelectionColor := Value;
Invalidate;
end;
{!!
<FS>THistogramBox.CompBar
<FM>Declaration<FC>
property CompBar: boolean;
<FM>Description<FN>
If True, a gradient bar is shown below the histogram graph.
!!}
procedure THistogramBox.SetCompBar(v: boolean);
begin
if v <> fCompBar then
begin
fCompBar := v;
Update;
end;
end;
{!!
<FS>THistogramBox.HistogramStyle
<FM>Declaration<FC>
property HistogramStyle: <A THistogramStyle>;
<FM>Description<FN>
Specifies the styling of the histogram: Solid bars, single lines or filled lines.
Default: hsBars
!!}
procedure THistogramBox.SetHistogramStyle(v: THistogramStyle);
begin
if v <> fHistogramStyle then
begin
fHistogramStyle := v;
Update;
end;
end;
procedure THistogramBox.SetHistogramKind(v: THistogramKind);
begin
if v <> fHistKind then
begin
fHistKind := v;
Update;
end;
end;
function ConvHistKindToHistContent(histKind: THistogramKind): TIEHistogramContent;
begin
result := [];
if hkRed in histKind then result := result + [iehcRed];
if hkGreen in histKind then result := result + [iehcGreen];
if hkBlue in histKind then result := result + [iehcBlue];
if hkGray in histKind then result := result + [iehcGray];
if hkHue in histKind then result := result + [iehcHue];
end;
{!!
<FS>THistogramBox.UpdateFromBitmap
<FM>Declaration<FC>
procedure UpdateFromBitmap(Bitmap: TIEBitmap);
<FM>Description<FN>
Updates the histogram from the specified bitmap.
<FM>Example<FC>
HistogramBox1.UpdateFromBitmap( ImageEnView1.IEBitmap );
!!}
procedure THistogramBox.UpdateFromBitmap(Bitmap: TIEBitmap);
var
proc: TImageEnProc;
Hist: TIEHistogram;
begin
proc := TImageEnProc.CreateFromBitmap(Bitmap);
try
Hist := proc.GetHistogram(ConvHistKindToHistContent(fHistKind));
UpdateFromHistogram(Hist);
finally
proc.Free();
end;
end;
{!!
<FS>THistogramBox.Clear
<FM>Declaration<FC>
procedure Clear();
<FM>Description<FN>
Reset the histogram to null values.
<FM>Example<FC>
HistogramBox1.Clear();
!!}
procedure THistogramBox.Clear();
var
i: integer;
Hist: TIEHistogram;
begin
SetLength(Hist, 256);
for i := 0 to 255 do
begin
Hist[i].r := 0;
Hist[i].g := 0;
Hist[i].b := 0;
Hist[i].Gray := 0;
Hist[i].Hue := 0;
end;
UpdateFromHistogram( Hist );
end;
// rebuild histogram from current selection (needs AttachedImageEnProc)
procedure THistogramBox.Update();
var
i: integer;
Hist: TIEHistogram;
function GetRandom : integer;
begin
if I <= 128 then
Result := I
else
Result := 256 - I;
Result := Result + random(128);
end;
begin
if (assigned(fAIEP) and assigned(fAIEP.AttachedIEBitmap)) or (csDesigning in ComponentState) then
begin
if csDesigning in ComponentState then
begin
SetLength(Hist, 256);
for i := 0 to 255 do
begin
Hist[i].r := GetRandom;
Hist[i].g := GetRandom;
Hist[i].b := GetRandom;
Hist[i].Gray := GetRandom;
Hist[i].Hue := GetRandom;
end;
UpdateFromHistogram(Hist);
end
else
if Assigned( fAIEP.AttachedImageEn ) and ( fAIEP.AttachedImageEn is TImageEnView ) and
TImageEnView( fAIEP.AttachedImageEn ).IsEmpty2 then
Clear()
else
begin
Hist := fAIEP.GetHistogram(ConvHistKindToHistContent(fHistKind));
UpdateFromHistogram(Hist);
end;
end;
end;
{!!
<FS>THistogramBox.UpdateFromHistogram
<FM>Declaration<FC>
procedure UpdateFromHistogram(Histogram: <A TIEHistogram>);
<FM>Description<FN>
Updates content from the specified histogram.
!!}
procedure THistogramBox.UpdateFromHistogram(Histogram: TIEHistogram);
const
Horz_Text_Margin = 4;
var
MaxV: dword;
q, w, e, dv: integer;
dx, dy, x1: integer;
xx: integer;
sz: TSize;
compdy: integer;
px: pRGBROW;
CVS: TIECanvas;
histlen: integer;
rgb: TRGB;
Offset: Integer;
function ScaleValue(Value: integer): integer;
begin
case fScale of
iehsLinear,
iehsLinearClipped:
result := round(Value / MaxV * dy);
iehsLogarithmic:
result := round( ln(1 + Value) / ln(1 + MaxV) * dy );
else
result := 0;
end;
end;
function IEHistogramItem(R, G, B, Gray, Hue: DWORD): THistogramItem;
begin
result.R := R;
result.G := G;
result.B := B;
result.Gray := Gray;
result.Hue := Hue;
end;
function CountOutBins(): integer;
var
i: integer;
begin
result := 0;
for i := 0 to histlen - 1 do
begin
if (hkRed in fHistKind) and (Histogram[i].r > MaxV) then
inc(result)
else if (hkGreen in fHistKind) and (Histogram[i].g > MaxV) then
inc(result)
else if (hkBlue in fHistKind) and (Histogram[i].b > MaxV) then
inc(result)
else if (hkGray in fHistKind) and (Histogram[i].Gray > MaxV) then
inc(result)
else if (hkHue in fHistKind) and (Histogram[i].Hue > MaxV) then
inc(result);
end;
end;
procedure CalcMax();
var
i: integer;
j: dword;
hmax, htot, hcnt: THistogramItem;
vmax, vtot, vcnt: dword;
begin
MaxV := 0;
hmax := IEHistogramItem(0, 0, 0, 0, 0);
htot := hmax;
hcnt := hmax;
for i := 0 to histlen - 1 do
begin
if hkRed in fHistKind then
begin
if Histogram[i].R > hmax.R then
hmax.R := Histogram[i].R;
if Histogram[i].R > 0 then
inc(hcnt.R);
inc(htot.R, Histogram[i].R);
end;
if hkGreen in fHistKind then
begin
if Histogram[i].G > hmax.G then
hmax.G := Histogram[i].G;
if Histogram[i].G > 0 then
inc(hcnt.G);
inc(htot.G, Histogram[i].G);
end;
if hkBlue in fHistKind then
begin
if Histogram[i].B > hmax.B then
hmax.B := Histogram[i].B;
if Histogram[i].B > 0 then
inc(hcnt.B);
inc(htot.B, Histogram[i].B);
end;
if hkGray in fHistKind then
begin
if Histogram[i].Gray > hmax.Gray then
hmax.Gray := Histogram[i].Gray;
if Histogram[i].Gray > 0 then
inc(hcnt.Gray);
inc(htot.Gray, Histogram[i].Gray);
end;
if hkHue in fHistKind then
begin
if Histogram[i].Hue > hmax.Hue then
hmax.Hue := Histogram[i].Hue;
if Histogram[i].Hue > 0 then
inc(hcnt.Hue);
inc(htot.Hue, Histogram[i].Hue);
end;
end;
vmax := imax(hmax.R, imax(hmax.G, imax(hmax.B, imax(hmax.Gray, hmax.Hue))));
vcnt := imax(hcnt.R, imax(hcnt.G, imax(hcnt.B, imax(hcnt.Gray, hcnt.Hue))));
vtot := imax(htot.R, imax(htot.G, imax(htot.B, imax(htot.Gray, htot.Hue))));
if (fScale = iehsLinearClipped) and (vtot <> 0) then
begin
j := 3;
repeat
MaxV := imin(vmax, j * vtot div vcnt);
inc(j);
until CountOutBins() < 4;
if (vmax - MaxV) / vtot < 0.05 then // ignore clip if difference is less than 5%
MaxV := vmax;
end
else
MaxV := vmax;
end;
begin
Bitmap.Canvas.Brush.Color := fBackground;
if Bitmap.Width = 1 then
begin
bitmap.Width := width;
bitmap.Height := height;
end;
Bitmap.Canvas.FillRect(rect(0, 0, Bitmap.Width, Bitmap.Height));
CVS := TIECanvas.Create(Bitmap.Canvas, true, true);
try
CVS.Brush.Color := fBackground;
CVS.Pen.Color := fBackground;
CVS.Pen.Style := psSolid;
histlen := length(Histogram);
CalcMax();
if histlen > 0 then
begin
if ((GetParentForm(self) <> nil) or (ParentWindow <> 0)) and HandleAllocated then
begin
dx := ClientWidth;
dy := ClientHeight;
end
else
begin
// client size still not available
dx := Width;
dy := Height;
end;
x1 := 0;
compdy := trunc(abs(Font.Height) * 1.2);
if fCompBar then
dec(dy, compdy + 2);
// LABELS
if (hlVertical in fLabels) then
begin
// paint vertical axis with labels
CVS.Font.Assign( Font );
sz := CVS.TextExtent( WideString(IEIntToFormattedStr( MaxV, 3 )) );
if MaxV div 2 < MaxV then
CVS.TextOut(0, dy div 2 - sz.cy div 2, string(IEIntToFormattedStr( MaxV div 2, 3 )) );
CVS.TextOut(0, 0, string(IEIntToFormattedStr( MaxV, 3 )) );
CVS.Pen.Color := Font.Color;
dec(dx, sz.cx + Horz_Text_Margin);
inc(x1, sz.cx + Horz_Text_Margin);
end;
if (hlHorizontal in fLabels) then
begin
// paint horizontal axis with labels
CVS.Font.Assign( Font );
sz := CVS.TextExtent(IntToStr(MaxV));
dv := histlen div 4;
for q := 0 to 3 do
CVS.TextOut(x1 + round(((q * dv) / histlen) * dx), dy - abs(Font.Height) - 1, IntToStr(q * dv));
q := CVS.TextWidth(IntToStr(histlen - 1));
CVS.TextOut(x1 + dx - q, dy - abs(Font.Height) - 1, IntToStr(histlen - 1));
dec(dy, sz.cy + 2);
// axis
CVS.MoveTo(x1 - 1, 0);
CVS.LineTo(x1 - 1, dy);
CVS.LineTo(x1 + dx, dy);
end;
//
fHistogramAbsXPos := x1;
// COMPBAR
if fCompBar then
begin
for w := 0 to compdy - 1 do
begin // row
px := bitmap.ScanLine[bitmap.height - w - 1];
for q := 0 to dx - 1 do
begin
px^[x1 + q].r := 0;
px^[x1 + q].g := 0;
px^[x1 + q].b := 0;
e := round(q / dx * 256);
if (hkRed in fHistKind) or (hkGray in fHistKind) then
px^[x1 + q].r := e;
if (hkGreen in fHistKind) or (hkGray in fHistKind) then
px^[x1 + q].g := e;
if (hkBlue in fHistKind) or (hkGray in fHistKind) then
px^[x1 + q].b := e;
if fHistKind = [hkHue] then
begin
HSV2RGB(px^[x1 + q], trunc(e / histlen * 360), 99, 50);
end;
end;
end;
end;
Offset := 0;
if fHistogramStyle = hsFilledLines then
Offset := -1;
// paint histogram on Bitmap
// hsBars and hsFilledLines
if ( MaxV > 0 ) and (( fHistogramStyle = hsBars ) or ( fHistogramStyle = hsFilledLines )) then
for xx := 0 to dx - 1 do
begin
q := trunc(xx / dx * histlen);
if hkRed in fHistKind then
begin
if fHistogramStyle = hsFilledLines then
CVS.Pen.Color := IEAverageColor( Background, clRed )
else
CVS.Pen.Color := clRed;
CVS.MoveTo(xx + x1, dy - 1);
e := ScaleValue(Histogram[q].r);
if (Histogram[q].r > 0) and (e = 0) then
e := 1;
CVS.LineTo(xx + x1, dy - 1 - e - Offset);
end;
if hkGreen in fHistKind then
begin
if fHistogramStyle = hsFilledLines then
CVS.Pen.Color := IEAverageColor( Background, clGreen )
else
CVS.Pen.Color := clGreen;
CVS.MoveTo(xx + x1, dy - 1);
e := ScaleValue(Histogram[q].g);
if (Histogram[q].g > 0) and (e = 0) then
e := 1;
CVS.LineTo(xx + x1, dy - 1 - e - Offset);
end;
if hkBlue in fHistKind then
begin
if fHistogramStyle = hsFilledLines then
CVS.Pen.Color := IEAverageColor( Background, clBlue )
else
CVS.Pen.Color := clBlue;
CVS.MoveTo(xx + x1, dy - 1);
e := ScaleValue(Histogram[q].b);
if (Histogram[q].b > 0) and (e = 0) then
e := 1;
CVS.LineTo(xx + x1, dy - 1 - e - Offset);
end;
if hkGray in fHistKind then
begin
if fHistogramStyle = hsFilledLines then
CVS.Pen.Color := IEAverageColor( Background, fGrayColor )
else
CVS.Pen.Color := fGrayColor;
CVS.MoveTo(xx + x1, dy - 1);
e := ScaleValue(Histogram[q].Gray);
if (Histogram[q].Gray > 0) and (e = 0) then
e := 1;
CVS.LineTo(xx + x1, dy - 1 - e - Offset);
end;
if hkHue in fHistKind then
begin
HSV2RGB(rgb, trunc(q / histlen * 360), 99, 50);
if fHistogramStyle = hsFilledLines then
CVS.Pen.Color := IEAverageColor( Background, TRGB2TCOLOR(rgb) )
else
CVS.Pen.Color := TRGB2TCOLOR(rgb);
CVS.MoveTo(xx + x1, dy - 1);
e := ScaleValue(Histogram[q].Hue);
if (Histogram[q].Hue > 0) and (e = 0) then
e := 1;
CVS.LineTo(xx + x1, dy - 1 - e - Offset);
end;
end;
// hsLines and hsFilledLines
if ( MaxV > 0 ) and (( fHistogramStyle = hsLines ) or ( fHistogramStyle = hsFilledLines )) then
begin
if hkRed in fHistKind then
begin
CVS.Pen.Color := clRed;
CVS.MoveTo(x1, dy - 1);
for xx := 0 to dx - 1 do
begin
q := trunc(xx / dx * histlen);
CVS.LineTo(xx + x1, dy - 1 - ScaleValue(Histogram[q].r) + 1);
end;
end;
if hkGreen in fHistKind then
begin
CVS.Pen.Color := clGreen;
CVS.MoveTo(x1, dy - 1);
for xx := 0 to dx - 1 do
begin
q := trunc(xx / dx * histlen);
CVS.LineTo(xx + x1, dy - 1 - ScaleValue(Histogram[q].g) + 1);
end;
end;
if hkBlue in fHistKind then
begin
CVS.Pen.Color := clBlue;
CVS.MoveTo(x1, dy - 1);
for xx := 0 to dx - 1 do
begin
q := trunc(xx / dx * histlen);
CVS.LineTo(xx + x1, dy - 1 - ScaleValue(Histogram[q].b) + 1);
end;
end;
if hkGray in fHistKind then
begin
CVS.Pen.Color := fGrayColor;
CVS.MoveTo(x1, dy - 1);
for xx := 0 to dx - 1 do
begin
q := trunc(xx / dx * histlen);
CVS.LineTo(xx + x1, dy - 1 - ScaleValue(Histogram[q].Gray) + 1);
end;
end;
if hkHue in fHistKind then
begin
CVS.MoveTo(x1, dy - 1);
for xx := 0 to dx - 1 do
begin
q := trunc(xx / dx * histlen);
HSV2RGB(rgb, trunc(q / histlen * 360), 99, 50);
CVS.Pen.Color := TRGB2TCOLOR(rgb);
CVS.LineTo(xx + x1, dy - 1 - ScaleValue(Histogram[q].Hue) + 1);
end;
end;
end;
end;
finally
CVS.Free;
end;
invalidate;
end;
procedure THistogramBox.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (AComponent = fAIEP) and (Operation = opRemove) then
fAIEP := nil;
end;
{!!
<FS>THistogramBox.AttachedImageEnProc
<FM>Declaration<FC>
property AttachedImageEnProc: <A TImageEnProc>;
<FM>Description<FN>
Use this property to attach a THistogramBox to a TImageEnProc object.
<FM>Example<FC>
HistogramBox1.AttachedImageEnProc := ImageEnView1.Proc;
HistogramBox1.Update; // now HistogramBox1 will display the histogram of an image attached to ImageEnProc1
!!}
procedure THistogramBox.SetAIEP(v: TImageEnProc);
begin
fAIEP := v;
if assigned(fAIEP) then
fAIEP.FreeNotification(self)
else
Clear;
end;
{!!
<FS>THistogramBox.HistogramKind
<FM>Declaration<FC>
property HistogramKind: <A THistogramKind>;
<FM>Description<FN>
Selects which channels are shown:
<TABLE>
<R> <H>Value</H> <H>Description</H> </R>
<R> <C>hkRed</C> <C>Red channel</C> </R>
<R> <C>hkGreen</C> <C>Green channel</C> </R>
<R> <C>hkBlue</C> <C>Blue channel</C> </R>
<R> <C>hkGray</C> <C>Gray-scale level</C> </R>
<R> <C>hkHue</C> <C>Hue (of HSV values)</C> </R>
</TABLE>
!!}
function THistogramBox.GetHistogramKind: THistogramKind;
begin
result := fHistKind;
end;
{!!
<FS>THistogramBox.Labels
<FM>Declaration<FC>
property Labels: <A THistogramLabels>;
<FM>Description<FN>
Specifies which labels (vertical and horizontal) are shown:
<TABLE>
<R> <H>Value</H> <H>Description</H> </R>
<R> <C>hlVertical</C> <C>Values are shown along the X axis</C> </R>
<R> <C>hlHorizontal</C> <C>Range is shown along the Y axis</C> </R>
</TABLE>
Default: [hlVertical, hlHorizontal];
!!}
function THistogramBox.GetLabels: THistogramLabels;
begin
result := fLabels;
end;
{!!
<FS>THistogramBox.MinSelected
<FM>Declaration<FC>
property MinSelected: Integer;
<FM>Description<FN>
If <A THistogramBox.MouseInteract> = <FC>mhSelectRange<FN> then <FC>MinSelected<FN> and <A THistogramBox.MaxSelected> specify the range of returned values.
If <A THistogramBox.MouseInteract> = <FC>mhSelectValue<FN> then <FC>MinSelected<FN> and <A THistogramBox.MaxSelected> return the same value.
Note: This valid range of <FC>MinSelected<FN> will depend on <A THistogramBox.HistogramKind>. If <A THistogramBox.HistogramKind> is <FC>[hkHue]<FN> then the range will be 0 to 359. Otherwise it will be 0 to 255. -1 can also be specified to clear the selection.
<FM>Example<FC>
// Select mid-point in color histogram
HistogramBox1.MinSelected := 128;
<FM>See Also<FC>
- <A THistogramBox.MaxSelected>
- <A THistogramBox.SelectionColor>
- <A THistogramBox.MouseInteract>
!!}
function THistogramBox.GetMinSelected: Integer;
begin
if fMouseMinX < 0 then
Result := -1
else
begin
if fHistKind = [hkHue] then
Result := MulDiv( 359, fMouseMinX - fHistogramAbsXPos, Width - fHistogramAbsXPos - 1 )
else
Result := MulDiv( 255, fMouseMinX - fHistogramAbsXPos, Width - fHistogramAbsXPos - 1 );
end;
end;
procedure THistogramBox.SetMinSelected(const Value: Integer);
begin
if fMouseMinX = Value then
exit;
if Value < 0 then
begin
// Deselect
fMouseMinX := -1;
fMouseMaxX := -1;
end
else
begin
if fHistKind = [hkHue] then
fMouseMinX := fHistogramAbsXPos + MulDiv( Width - fHistogramAbsXPos - 1, Value, 359 )
else
fMouseMinX := fHistogramAbsXPos + MulDiv( Width - fHistogramAbsXPos - 1, Value, 255 );
if fMouseMinX > Width - 1 then
fMouseMinX := Width - 1;
end;
Invalidate;
end;
{!!
<FS>THistogramBox.MaxSelected
<FM>Declaration<FC>
property MaxSelected: Integer;
<FM>Description<FN>
If <A THistogramBox.MouseInteract> = <FC>mhSelectRange<FN> then <A THistogramBox.MinSelected> and <FC>MaxSelected<FN> specify the range of returned values.
If <A THistogramBox.MouseInteract> = <FC>mhSelectValue<FN> then <A THistogramBox.MinSelected> and <FC>MaxSelected<FN> return the same value.
Note: This valid range of <FC>MaxSelected<FN> will depend on <A THistogramBox.HistogramKind>. If <A THistogramBox.HistogramKind> is <FC>[hkHue]<FN> then the range will be 0 to 359. Otherwise it will be 0 to 255. -1 can also be specified to clear the selection.
<FM>Example<FC>
// Select from 100 to 200
HistogramBox1.MouseInteract := mhSelectRange;
HistogramBox1.MinSelected := 100;
HistogramBox1.MaxSelected := 200;
<FM>See Also<FC>
- <A THistogramBox.MinSelected>
- <A THistogramBox.SelectionColor>
- <A THistogramBox.MouseInteract>
!!}
function THistogramBox.GetMaxSelected: Integer;
begin
If fMouseInteract = mhSelectValue then
Result := GetMinSelected
else
if fMouseMaxX < 0 then
Result := -1
else
begin
if fHistKind = [hkHue] then
Result := MulDiv( 359, fMouseMaxX - fHistogramAbsXPos, Width - fHistogramAbsXPos - 1 )
else
Result := MulDiv( 255, fMouseMaxX - fHistogramAbsXPos, Width - fHistogramAbsXPos - 1 );
end;
end;
procedure THistogramBox.SetMaxSelected(const Value: Integer);
begin
if fMouseMaxX = Value then
exit;
if Value < 0 then
begin
// Deselect
fMouseMinX := -1;
fMouseMaxX := -1;
end
else
begin
if fHistKind = [hkHue] then
fMouseMaxX := fHistogramAbsXPos + MulDiv( Width - fHistogramAbsXPos - 1, Value, 359 )
else
fMouseMaxX := fHistogramAbsXPos + MulDiv( Width - fHistogramAbsXPos - 1, Value, 255 );
if fMouseMaxX > Width - 1 then
fMouseMaxX := Width - 1;
end;
Invalidate;
end;
{!!
<FS>THistogramBox.Scale
<FM>Declaration<FC>
property Scale: <A THistogramScale>;
<FM>Description<FN>
Specifies the histogram plot scale.
Default value is iehsLinear. Use iehsLinearClipped or iehsLogarithmic on images with a heavy background color, which may create flat histograms.
<FM>Example<FC>
// Improve range of histogram display
HistogramBox1.Scale := iehsLinearClipped;
!!}
procedure THistogramBox.SetScale(Value: THistogramScale);
begin
if fScale <> Value then
begin
fScale := Value;
Update();
end;
end;
end.