Sordie.co.uk
libsassy/libSassy.Bitmaps.pas
Raw
{(
)) libSassy.Bitmaps
(( 2D Bitmap library
))
(( Copyright © Sordie Aranka Solomon-Smith 2015-2017
))
(( This work is made available under the terms of the Creative Commons
)) Attribution-NonCommercial-ShareAlike 3.0 Unported license
(( http://creativecommons.org/licenses/by-nc-sa/3.0/
)}
unit libSassy.Bitmaps;
{$DEFINE BitmapRangeCheck}
interface
uses
Winapi.Windows,
libSassy.Interfaces,
libSassy.Streams,
libSassy.Integers,
libSassy.Maths;
const
PixelFormat = 2498570;
type
{$REGION 'TCustomBitmap'}
TBitmapLockMode = (lmRead, lmWrite, lmUserBuf);
TBitmapLockModes = set of TBitmapLockMode;
TBitmapData = packed record
Width: Cardinal;
Height: Cardinal;
Stride: Integer;
PixelFormat: Integer;
Scan0: Pointer;
Reserved: PInteger;
Size: Cardinal;
LockMode: TBitmapLockModes;
LockX: Cardinal;
LockY: Cardinal;
end;
TBlkRender = procedure(const Colour: Cardinal; const Dest: Pointer; const Count, Stride: Integer);
TBltRender = procedure(const Source, Dest: Pointer; const Count, SourceStride: Integer);
TSubBitmap = class;
TCustomBitmap = class abstract(TInterface)
private
fBlkRender: TBlkRender;
fBltRender: TBltRender;
fFrames: array of array of TSubBitmap;
fFrameX: Integer;
fFrameY: Integer;
protected
procedure SetWidth (Value: Cardinal); virtual;
procedure SetHeight(Value: Cardinal); virtual;
public
BitmapData: TBitmapData;
Scanlines: array of Pointer;
constructor Create(const AWidth: Integer = 0; const AHeight: Integer = 0);
destructor Destroy; override;
function Resize(const AWidth, AHeight: Cardinal): Boolean; virtual;
function Lock (var ABitmapData: TBitmapData; const x, y, w, h: Integer; const LockMode: TBitmapLockModes): Boolean; virtual;
function Unlock(var ABitmapData: TBitmapData): Boolean; virtual;
procedure SetFrames(const x: Integer; const y: Integer = 1);
function Frames (const x: Integer; const y: Integer = 0): TSubBitmap; inline;
procedure Clear(const Colour: Cardinal = $FF000000);
function GetPixel(x, y: Integer): Cardinal;
procedure SetPixel(x, y: Integer; const Colour: Cardinal);
procedure DrawPixel(x, y: Single; const Colour: Cardinal);
procedure HLine(x, y, l: Integer; const Colour: Cardinal);
procedure VLine(x, y, l: Integer; const Colour: Cardinal);
procedure Line(x1, y1, x2, y2: Integer; const Colour: Cardinal);
procedure Bezier(x1, y1, x2, y2, x3, y3, x4, y4: Integer; const Colour: Cardinal; const Count: Integer = 0);
procedure Box (x, y, w, h: Integer; const Colour: Cardinal; const Fill: Boolean = False);
procedure Ellipse (x, y, w, h: Integer; const Colour: Cardinal; const Fill: Boolean = False);
procedure Triangle(x1, y1, x2, y2, x3, y3: Integer; const Colour: Cardinal; const Fill: Boolean = False);
procedure Polygon(x, y: Integer; const Points: array of TPoint; const Colour: Cardinal; const Fill: Boolean = False; const Close: Boolean = True);
procedure Geom(x, y, w, h: Integer; const Sides: Integer; const Angle: Single; const Colour: Cardinal; const Fill: Boolean = False);
procedure Star(x, y, w1, w2, h1, h2: Integer; const Points: Integer; const Angle: Single; const Colour: Cardinal; const Fill: Boolean = False);
procedure TiltedEllipse(x, y, w, h: Integer; const Tilt: Single; const Colour: Cardinal; const Fill: Boolean = False);
procedure TiltedGeom (x, y, w, h: Integer; const Sides: Integer; const Angle, Tilt: Single; const Colour: Cardinal; const Fill: Boolean = False);
procedure Render (const Bitmap: TCustomBitmap; const x, y: Integer; const FlipX: Boolean = False; const FlipY: Boolean = False);
procedure RenderAffine(const Bitmap: TCustomBitmap; const x1, y1, x2, y2, x3, y3, x4, y4: Integer);
procedure RenderAngle (const Bitmap: TCustomBitmap; const x, y: Integer; const Angle: Single);
procedure SwapRB;
procedure SetTransparentColour(const Colour: Cardinal = 0);
// TODO: Filters etc
function LoadFromStream(Stream: TStream): Boolean;
function SaveToStream (Stream: TStream; const Mime: String = ''; Quality: Integer = -1): Boolean;
function LoadFromFile(const FileName: String): Boolean;
function SaveToFile (const FileName: String; const Mime: String = ''; const Quality: Integer = -1): Boolean;
// TODO: function LoadFromResource(Module: THandle; ResourceID: Integer): Boolean;
property Width: Cardinal read BitmapData.Width write SetWidth;
property Height: Cardinal read BitmapData.Height write SetHeight;
property Stride: Integer read BitmapData.Stride;
property Bits: Pointer read BitmapData.Scan0;
property Size: Cardinal read BitmapData.Size;
property BlkRender: TBlkRender read fBlkRender write fBlkRender;
property BltRender: TBltRender read fBltRender write fBltRender;
property FrameCount: Integer read fFrameX;
property FrameCountX: Integer read fFrameX;
property FrameCountY: Integer read fFrameY;
end;
{$ENDREGION}
{$REGION 'TSubBitmap'}
TSubBitmap = class(TCustomBitmap)
private
fParent: TCustomBitmap;
public
constructor Create(const AParent: TCustomBitmap; const x, y, w, h: Integer);
destructor Destroy; override;
property Parent: TCustomBitmap read fParent;
end;
{$ENDREGION}
{$REGION 'TBitmap'}
TBitmap = class(TCustomBitmap)
public
function Resize(const AWidth, AHeight: Cardinal): Boolean; override;
end;
{$ENDREGION}
{$REGION 'TDIB'}
TDIB = class(TCustomBitmap)
private
fHandle: HBITMAP;
fInfo: BITMAPINFO;
fHeader: BITMAPINFOHEADER;
fDC: HDC;
function GetDC: HDC;
public
function Resize(const AWidth, AHeight: Cardinal): Boolean; override;
procedure BltToDC(const DC: HDC; const x: Integer = 0; const y: Integer = 0; const w: Integer = 0; const h: Integer = 0);
procedure CreateDC;
procedure ReleaseDC;
property Handle: HBITMAP read fHandle;
property DC: HDC read GetDC;
end;
{$ENDREGION}
{$REGION 'Render'}
procedure BltCopy(const Source, Dest: Pointer; const Count, SourceStride: Integer);
procedure BlkCopy(const Colour: Cardinal; const Dest: Pointer; const Count, Stride: Integer);
const
AlphaBias = $00800080;
procedure BltAlpha(const Source, Dest: Pointer; const Count, SourceStride: Integer);
procedure BlkAlpha(const Colour: Cardinal; const Dest: Pointer; const Count, Stride: Integer);
procedure BltAlphaMMX(const Source, Dest: Pointer; const Count, SourceStride: Integer);
procedure BlkAlphaMMX(const Colour: Cardinal; const Dest: Pointer; const Count, Stride: Integer);
{$ENDREGION}
{$REGION 'GDI+'}
type
TBox = record
x, y: Integer;
w, h: Integer;
end;
TEncoderParameter = packed record
GUID: TGUID;
NumberOfValues: Cardinal;
ParamType: Cardinal;
Value: Pointer;
end;
PEncoderParameters = ^TEncoderParameters;
TEncoderParameters = packed record
Count: Cardinal;
Param: array of TEncoderParameter;
end;
TImageCodecInfo = packed record
CLSID: TGUID;
FormatID: TGUID;
CodecName: PWideChar;
DllName: PWideChar;
FormatDescription: PWideChar;
FilenameExtension: PWideChar;
MimeType: PWideChar;
Flags: Cardinal;
Version: Cardinal;
SigCount: Cardinal;
SigSize: Cardinal;
SigPattern: PByte;
SigMask: PByte;
end;
PImageCodecInfos = ^TImageCodecInfos;
TImageCodecInfos = array[0..255] of TImageCodecInfo;
TGDIPlusStartupInput = packed record
Version: Cardinal;
DebugCallback: Pointer;
SuppressBackgroundThread: LongBool;
SuppressExternalCodecs: LongBool;
end;
PGDIPlusStartupOutput = ^TGDIPlusStartupOutput;
TGDIPlusStartupOutput = packed record
NotificationHook: Pointer;
NotificationUnhook: Pointer;
end;
TGDIPlusObject = Pointer;
const
GDIPlus = 'GDIPlus.dll';
function GdiplusStartup(out Token: LongWord; const Input: TGDIPlusStartupInput; Output: PGDIPlusStartupOutput): Integer; stdcall; external GDIPlus;
procedure GdiplusShutdown(Token: LongWord); stdcall; external GDIPlus;
function GdipCreateBitmapFromScan0(Width, Height, Stride: Integer; Format: Integer; Scan0: Pointer; var Bitmap: TGDIPlusObject): Integer; stdcall; external GDIPlus;
function GdipLoadImageFromStream(Stream: IStream; var Image: TGDIPlusObject): Integer; stdcall; external GDIPlus;
function GdipSaveImageToStream(Image: TGDIPlusObject; Stream: IStream; const CLSIDEncoder: TGUID; const Params: PEncoderParameters): Integer; stdcall; external GDIPlus;
function GdipLoadImageFromFile(const FileName: PWideChar; var Image: TGDIPlusObject): Integer; stdcall; external GDIPlus;
function GdipSaveImageToFile(Image: TGDIPlusObject; const FileName: PWideChar; const CLSIDEncoder: TGUID; const Params: PEncoderParameters): Integer; stdcall; external GDIPlus;
function GdipGetImageEncodersSize(var NumEncoders, Size: LongWord): Integer; stdcall; external GDIPlus;
function GdipGetImageEncoders(NumEncoders, Size: LongWord; Encoders: PImageCodecInfos): Integer; stdcall; external GDIPlus;
function GdipDisposeImage(Image: TGDIPlusObject): Integer; stdcall; external GDIPlus;
function GdipGetImageWidth(Image: TGDIPlusObject; var Width: Cardinal): Integer; stdcall; external GDIPlus;
function GdipGetImageHeight(Image: TGDIPlusObject; var Height: Cardinal): Integer; stdcall; external GDIPlus;
function GdipBitmapLockBits(Bitmap: TGDIPlusObject; const Box: TBox; Flags: Cardinal; Format: Integer; var BitmapData: TBitmapData): Integer; stdcall; external GDIPlus;
function GdipBitmapUnlockBits(Bitmap: TGDIPlusObject; var BitmapData: TBitmapData): Integer; stdcall; external GDIPlus;
function InitializeGDIPlus: Boolean;
{$ENDREGION}
implementation
{$REGION 'Render'}
procedure BltCopy;
begin
Move(Source^, Dest^, Count shl 2);
end;
procedure BlkCopy;
var
i: Integer;
P: ^Cardinal;
begin
P := Dest;
for i := 1 to Count do
begin
P^ := Colour;
inc(P);
end;
end;
procedure BltAlpha;
asm
test ecx, ecx
js @4
push ebx
push esi
push edi
mov esi, eax
mov edi, edx
@1:
mov eax, [esi]
test eax, $FF000000
jz @3
push ecx
mov ecx, eax
shr ecx, 24
cmp ecx, $FF
jz @2
mov ebx, eax
and eax, $00FF00FF
and ebx, $FF00FF00
imul eax, ecx
shr ebx, 8
imul ebx, ecx
add eax, AlphaBias
and eax, $FF00FF00
shr eax, 8
add ebx, AlphaBias
and ebx, $FF00FF00
or eax, ebx
mov edx, [edi]
xor ecx, $000000FF
mov ebx, edx
and edx, $00FF00FF
and ebx, $FF00FF00
imul edx, ecx
shr ebx, 8
imul ebx, ecx
add edx, AlphaBias
and edx, $FF00FF00
shr edx, 8
add ebx, AlphaBias
and ebx, $FF00FF00
or ebx, edx
add eax, ebx
@2:
mov [edi], eax
pop ecx
@3:
add esi, SourceStride
add edi, 4
dec ecx
jnz @1
pop edi
pop esi
pop ebx
@4:
end;
procedure BlkAlpha;
var
c1, c2: Cardinal;
begin
if Count = 0 then Exit;
asm
push ebx
push edi
mov edi, Dest
mov eax, Colour
mov ecx, eax
shr ecx, 24
mov ebx, eax
and eax, $00FF00FF
and ebx, $FF00FF00
imul eax, ecx
shr ebx, 8
imul ebx, ecx
add eax, AlphaBias
and eax, $FF00FF00
shr eax, 8
add ebx, AlphaBias
and ebx, $FF00FF00
or eax, ebx
mov c1, ecx
mov c2, eax
mov ecx, Count
@1:
push ecx
mov ecx, c1
mov eax, c2
mov edx, [edi]
xor ecx, $000000FF
mov ebx, edx
and edx, $00FF00FF
and ebx, $FF00FF00
imul edx, ecx
shr ebx, 8
imul ebx, ecx
add edx, AlphaBias
and edx, $FF00FF00
shr edx, 8
add ebx, AlphaBias
and ebx, $FF00FF00
or ebx, edx
add eax, ebx
mov [edi], eax
pop ecx
add edi, Stride
dec ecx
jnz @1
pop edi
pop ebx
end;
end;
{$ENDREGION}
{$REGION 'GDI+'}
var
GDIPlusToken: LongWord = 0;
function InitializeGDIPlus;
const
StartupInput: TGDIPlusStartupInput = (Version: 1;
DebugCallback: nil;
SuppressBackgroundThread: False;
SuppressExternalCodecs: False);
begin
if GDIPlusToken <> 0 then exit(True);
Result := GDIPlusStartup(GDIPlusToken, StartupInput, nil) = 0;
end;
{$ENDREGION}
{$REGION 'TCustomBitmap'}
procedure TCustomBitmap.SetWidth;
begin
Resize(Value, Height);
end;
procedure TCustomBitmap.SetHeight;
begin
Resize(Width, Value);
end;
constructor TCustomBitmap.Create;
begin
inherited Create;
fFrameX := 0;
fFrameY := 0;
SetFrames(0, 0);
fBlkRender := BlkCopy;
fBltRender := BltCopy;
if (AWidth <> 0) and (AHeight <> 0) then
Resize(AWidth, AHeight);
end;
destructor TCustomBitmap.Destroy;
begin
Resize(0, 0);
inherited;
end;
function TCustomBitmap.Resize;
var
i: Integer;
begin
Result := True;
SetFrames(0, 0);
BitmapData.Width := AWidth;
BitmapData.Height := AHeight;
BitmapData.PixelFormat := PixelFormat;
BitmapData.Size := Cardinal(BitmapData.Stride) * AHeight;
SetLength(Scanlines, AHeight + 1);
if (AWidth = 0) or (AHeight = 0) then exit;
for i := 0 to AHeight do
Scanlines[i] := Pointer(Cardinal(BitmapData.Scan0) + Cardinal(BitmapData.Stride * i));
end;
function TCustomBitmap.Lock;
var
i: Integer;
xx, yy: Integer;
ww, hh: Integer;
p1, p2: PByte;
begin
FillChar(ABitmapData, 0, sizeof(ABitmapData));
if (x >= Integer(BitmapData.Width)) or (y >= Integer(BitmapData.Height)) then exit(False);
xx := x; yy := y;
ww := w; hh := h;
AdjustClamp(xx, ww, 0, BitmapData.Width);
AdjustClamp(yy, hh, 0, BitmapData.Height);
ABitmapData.Width := ww;
ABitmapData.Height := hh;
ABitmapData.LockMode := LockMode;
ABitmapData.LockX := xx;
ABitmapData.LockY := yy;
ABitmapData.Reserved := BitmapData.Scan0;
if lmUserBuf in LockMode then
begin
if (BitmapData.PixelFormat <> PixelFormat) or (Integer(ABitmapData.Width) <> ww) or (Integer(ABitmapData.Height) <> hh) then exit(False);
if lmRead in LockMode then
begin
p1 := Pointer(Integer(Scanlines[yy]) + (xx shl 2));
p2 := BitmapData.Scan0;
for i := 0 to hh - 1 do
begin
Move(p1^, p2^, ww shl 2);
inc(p1, BitmapData.Stride);
inc(p2, ABitmapData.Stride);
end;
end;
end
else
begin
ABitmapData.Stride := BitmapData.Stride;
ABitmapData.Scan0 := Pointer(Integer(Scanlines[yy]) + (xx shl 2));
ABitmapData.PixelFormat := BitmapData.PixelFormat;
end;
Result := True;
end;
function TCustomBitmap.Unlock;
var
i: Integer;
p1, p2: PByte;
begin
Result := ABitmapData.Reserved = BitmapData.Scan0;
if not Result then exit;
if (lmUserBuf in ABitmapData.LockMode) and (lmWrite in ABitmapData.LockMode) then
begin
p1 := Pointer(Integer(Scanlines[ABitmapData.LockX]) + Integer(ABitmapData.LockY shl 2));
p2 := ABitmapData.Scan0;
for i := 0 to ABitmapData.Height - 1 do
begin
Move(p2^, p1^, ABitmapData.Width shl 2);
inc(p1, BitmapData.Stride);
inc(p2, ABitmapData.Stride);
end;
end;
FillChar(ABitmapData, 0, sizeof(ABitmapData));
end;
procedure TCustomBitmap.SetFrames;
var
xx, yy: Integer;
ww, hh: Integer;
begin
for yy := 0 to fFrameY - 1 do
for xx := 0 to fFrameX - 1 do
fFrames[yy, xx].Free;
if (y <= 0) or (x <= 0) then
begin
fFrameX := 0;
fFrameY := 0;
SetLength(fFrames, 0, 0);
exit;
end;
fFrameX := x;
fFrameY := y;
SetLength(fFrames, y, x);
ww := Integer(Width) div x;
hh := Integer(Height) div y;
for yy := 0 to fFrameY - 1 do
for xx := 0 to fFrameX - 1 do
fFrames[yy, xx] := TSubBitmap.Create(Self, ww * xx, hh * yy, ww, hh);
end;
function TCustomBitmap.Frames;
begin
Result := fFrames[y, x];
end;
procedure TCustomBitmap.Clear;
var
i: Integer;
p: Pointer;
begin
for i := 0 to Height - 1 do
begin
p := Scanlines[i];
fBlkRender(Colour, p, Width, BitmapData.Stride);
end;
end;
function TCustomBitmap.GetPixel;
begin
{$IFDEF BitmapRangeCheck}
if (x < 0) or (x > Integer(BitmapData.Width - 1)) or (y < 0) or (y > Integer(BitmapData.Height - 1)) then Exit(0);
{$ENDIF BitmapRangeCheck}
Result := Cardinal(Pointer(Integer(Scanlines[y]) + (x shl 2))^);
end;
procedure TCustomBitmap.SetPixel;
var
p: Pointer;
begin
{$IFDEF BitmapRangeCheck}
if (x < 0) or (x > Integer(BitmapData.Width - 1)) or (y < 0) or (y > Integer(BitmapData.Height - 1)) then Exit;
{$ENDIF BitmapRangeCheck}
p := Pointer(Integer(Scanlines[y]) + (x shl 2));
if @fBlkRender = nil then
Cardinal(p^) := Colour
else
BlkRender(Colour, p, 1, 1);
end;
procedure TCustomBitmap.DrawPixel;
const
Lightness = 0.25;
procedure BlendPixel(xx, yy: Integer; a: Single);
var
cc: LongWord;
aa: LongWord;
begin
cc := Colour and $FFFFFF;
aa := (Colour shr 24) and $FF;
aa := Clamp(round(aa * (a + Lightness)));
cc := cc or (aa shl 24);
SetPixel(xx, yy, cc);
end;
var
ix, iy: Integer;
ex, ey: Single;
a1, a2,
a3, a4: Single;
begin
if ((Colour {and $FF00000}) shr 24) = 0 then exit;
ix := trunc(x); ex := x - ix;
iy := trunc(y); ey := y - iy;
a1 := (1 - ex) * (1 - ey);
a2 := ex * (1 - ey);
a3 := (1 - ex) * ey ;
a4 := ex * ey ;
BlendPixel(ix, iy, a1);
BlendPixel(ix + 1, iy, a2);
BlendPixel(ix, iy + 1, a3);
BlendPixel(ix + 1, iy + 1, a4);
end;
procedure TCustomBitmap.HLine;
var
p: Pointer;
begin
{$IFDEF BitmapRangeCheck}
if (y < 0) or (y > Integer(BitmapData.Height - 1)) then exit;
if x > Integer(BitmapData.Width - 1) then exit;
if x < 0 then
begin
l := l + x;
x := 0;
end;
if l <= 0 then exit;
if (x + l) > Integer(BitmapData.Width - 1) then l := Integer(BitmapData.Width) - x;
{$ENDIF BitmapRangeCheck}
p := Pointer(Integer(Scanlines[y]) + (x shl 2));
fBlkRender(Colour, p, l, 4);
end;
procedure TCustomBitmap.VLine;
var
p: Pointer;
s: Integer;
begin
{$IFDEF BitmapRangeCheck}
if (x < 0) or (x > Integer(BitmapData.Width - 1)) then exit;
if y > Integer(BitmapData.Height - 1) then exit;
if y < 0 then
begin
l := l + y;
y := 0;
end;
if l <= 0 then exit;
if (y + l) > Integer(BitmapData.Height - 1) then l := Integer(BitmapData.Height) - y;
{$ENDIF BitmapRangeCheck}
p := Pointer(Integer(Scanlines[y]) + (x shl 2));
s := BitmapData.Stride;
fBlkRender(Colour, p, l, s);
end;
procedure TCustomBitmap.Line;
var
dx, dy, iy: Integer;
f1, f2, f3: Integer;
i: Integer;
begin
dx := x2 - x1;
dy := y2 - y1;
if dx < 0 then
begin
dx := -dx; x1 := x2;
dy := -dy; y1 := y2;
end;
if dy < 0 then
begin
dy := -dy;
iy := -1;
end
else iy := 1;
if dx > dy then
begin
f1 := dy shl 1;
f2 := f1 - dx;
f3 := f2 - dx;
for i := 0 to dx do
begin
SetPixel(x1, y1, Colour);
inc(x1);
if f2 < f1 then
inc(f2, f1)
else
begin
inc(f2, f3);
inc(y1, iy);
end;
end;
end
else
begin
f1 := dx shl 1;
f2 := f1 - dy;
f3 := f2 - dy;
for i := 0 to dy do
begin
SetPixel(x1, y1, Colour);
inc(y1, iy);
if f2 < f1 then
inc(f2, f1)
else
begin
inc(f2, f3);
inc(x1);
end;
end;
end;
end;
procedure TCustomBitmap.Bezier;
procedure CalcBezier(t: Real; var x, y: Integer);
begin
x := round(Power(1 - t, 3) * x1 + 3 * t * Power(1 - t, 2) * x2 + 3 * t * t * (1 - t) * x3 + Power(t, 3) * x4);
y := round(Power(1 - t, 3) * y1 + 3 * t * Power(1 - t, 2) * y2 + 3 * t * t * (1 - t) * y3 + Power(t, 3) * y4);
end;
var
Resolution, t: Real;
xc, yc, sx, sy: Integer;
c: Integer;
begin
if Count = 0 then c := 50 else c := Count;
Resolution := 1 / c;
sx := x1; sy := y1;
t := 0;
while t < 1 do
begin
CalcBezier(t, xc, yc);
Line(sx, sy, xc, yc, Colour); sx := xc; sy := yc;
t := t + Resolution;
end;
Line(sx, sy, x4, y4, Colour);
end;
procedure TCustomBitmap.Box;
var
i: Integer;
begin
if not fill then
begin
HLine(x, y, w, Colour);
HLine(x, y + h - 1, w, Colour);
VLine(x, y, h, Colour);
VLine(x + w - 1, y, h, Colour)
end
else
for i := y to y + h - 1 do
HLine(x, i, w, Colour);
end;
procedure TCustomBitmap.Ellipse;
var
xx, yy: Integer;
x2, j: Integer;
begin
if (w = 0) or (h = 0) then exit;
if h = 1 then
begin
HLine(x, y, w, Colour);
exit;
end;
if w = 1 then
begin
VLine(x, y, h, Colour);
exit;
end;
xx := 0;
if Fill then
begin
HLine(x - w, y, (w * 2), Colour);
for yy := 1 to (h - 1) do
begin
xx := round(w / (h - 1) * Sqrt((Sqr(Integer(h - 1))) - Sqr(yy - 0.5)));
HLine(x - xx, y + yy, (xx * 2), Colour);
HLine(x - xx, y - yy, (xx * 2), Colour);
end;
end
else
begin
x2 := w;
for yy := 0 to (h - 1) do
begin
xx := round(w / (h - 1) * Sqrt(Sqr(h - 1) - Sqr(yy - 0.5)));
for j := xx to x2 do
begin
SetPixel(x + j, y + yy, Colour);
SetPixel(x - j, y + yy, Colour);
SetPixel(x + j, y - yy, Colour);
SetPixel(x - j, y - yy, Colour);
end;
x2 := xx;
end;
for j := 0 to xx - 1 do
begin
SetPixel(x + j, y + h, Colour);
SetPixel(x - j, y + h, Colour);
SetPixel(x + j, y - h, Colour);
SetPixel(x - j, y - h, Colour);
end;
end;
end;
procedure TCustomBitmap.Triangle;
var
x, MinY, MaxY: Integer;
ax, bx, yy: Integer;
p1, q1, p2, q2, p3, q3: Integer;
begin
if not Fill then
begin
Line(x1, y1, x2, y2, Colour);
Line(x2, y2, x3, y3, Colour);
Line(x3, y3, x1, y1, Colour);
Exit;
end;
MinY := y1; MaxY := y1;
if y2 < MinY then MinY := y2;
if y2 > MaxY then MaxY := y2;
if y3 < MinY then MinY := y3;
if y3 > MaxY then MaxY := y3;
p1 := x1 - x3; q1 := y1 - y3;
p2 := x2 - x1; q2 := y2 - y1;
p3 := x3 - x2; q3 := y3 - y2;
for yy := MinY to MaxY do
begin
ax := Width;
bx := -1;
if (y3 >= yy) or (y1 >= yy) then
if (y3 <= yy) or (y1 <= yy) then
if not (y3 = y1) then
begin
x := (yy - y3) * p1 div q1 + x3;
if x < ax then ax := x;
if x > bx then bx := x;
end;
if (y1 >= yy) or (y2 >= yy) then
if (y1 <= yy) or (y2 <= yy) then
if not (y1 = y2) then
begin
x := (yy - y1) * p2 div q2 + x1;
if x < ax then ax := x;
if x > bx then bx := x;
end;
if (y2 >= yy) or (y3 >= yy) then
if (y2 <= yy) or (y3 <= yy) then
if not (y2 = y3) then
begin
x := (yy - y2) * p3 div q3 + x2;
if x < ax then ax := x;
if x > bx then bx := x;
end;
if ax <= bx then HLine(ax, yy, bx - ax, Colour);
end;
end;
procedure TCustomBitmap.Polygon;
var
xx, i, MinY, MaxY: Integer;
b, ax, bx, yy: Integer;
p, q: Integer;
begin
if not Fill then
begin
for i := (low(Points) + 1) to high(Points) do
Line(x + Points[i - 1].x, y + Points[i - 1].y, x + Points[i].x, y + Points[i].y, Colour);
if Close then Line(x + Points[high(Points)].x, y + Points[high(Points)].y, x + Points[low(Points)].x, y + Points[low(Points)].y, Colour);
Exit;
end;
MinY := Points[low(Points)].y;
MaxY := Points[low(Points)].y;
for i := (low(Points) + 1) to high(Points) do
begin
if Points[i].y < MinY then MinY := Points[i].y;
if Points[i].y > MaxY then MaxY := Points[i].y;
end;
for yy := MinY to MaxY do
begin
ax := Width;
bx := -1;
for i := low(Points) + 1 to high(Points) do
begin
b := i - 1; if b < low(Points) then b := high(Points);
p := Points[i].x - Points[b].x;
q := Points[i].y - Points[b].y;
if (Points[b].y >= yy) or (Points[i].y >= yy) then
if (Points[b].y <= yy) or (Points[i].y <= yy) then
if not (Points[b].y = Points[i].y) then
begin
xx := (yy - Points[b].y) * p div q + Points[b].x;
if xx < ax then ax := xx;
if xx > bx then bx := xx;
end;
end;
if ax <= bx then HLine(x + ax, y + yy, bx - ax, Colour);
end;
end;
procedure TCustomBitmap.Geom;
var
i: Integer;
s: Single;
Poly: array of TPoint;
begin
SetLength(Poly, Sides);
s := (PI * 2) / Sides;
for i := 0 to Sides do
begin
Poly[i].x := round(sin((s * i) + Angle) * w);
Poly[i].y := round(cos((s * i) + Angle) * h);
end;
Polygon(x, y, Poly, Colour, Fill, True);
end;
procedure TCustomBitmap.Star;
var
i: Integer;
s: Single;
w, h: Integer;
Poly: array of TPoint;
begin
SetLength(Poly, Points * 2);
s := PI / Points;
for i := 0 to Points * 2 do
begin
if (i mod 2) = 1 then
begin
w := w1;
h := h1;
end
else
begin
w := w2;
h := h2;
end;
Poly[i].X := round(sin((s * i) + Angle) * w);
Poly[i].Y := round(cos((s * i) + Angle) * h);
end;
Polygon(x, y, Poly, Colour, Fill, True);
end;
procedure TCustomBitmap.TiltedEllipse;
begin
TiltedGeom(x, y, w, h, w + h, 0, Tilt, Colour, Fill);
end;
procedure TCustomBitmap.TiltedGeom;
var
i: Integer;
theta: Single;
xx, yy: Integer;
Points: array of TPoint;
begin
SetLength(Points, Sides);
for i := 0 to Sides do
begin
theta := 360 * (i / Sides) * (PI / 180);
xx := x + round(sin(theta + Angle) * w);
yy := y + round(cos(theta + Angle) * h);
Points[i].x := round(x + (xx - x) * cos(Tilt) - (yy - y) * sin(Tilt));
Points[i].Y := round(x + (xx - x) * sin(Tilt) + (yy - y) * cos(Tilt));
end;
Polygon(0, 0, Points, Colour, Fill, True);
end;
procedure TCustomBitmap.Render;
var
Scan: Integer;
xx, yy: Integer;
ww, hh: Integer;
dx, dy: Integer;
DestData: TBitmapData;
SourceData: TBitmapData;
PDest: PByte;
PSource: PByte;
begin
ww := Bitmap.Width; xx := x;
hh := Bitmap.Height; yy := y;
AdjustClampDelta(xx, ww, 0, Width, dx);
AdjustClampDelta(yy, hh, 0, Height, dy);
if not Lock(DestData, xx, yy, ww, hh, [lmRead, lmWrite]) then exit;
try
if not Bitmap.Lock(SourceData, dx, dy, ww, hh, [lmRead]) then exit;
try
PDest := DestData.Scan0;
if FlipY then
PSource := Pointer(Cardinal(SourceData.Scan0) + (Cardinal(Bitmap.BitmapData.Stride) * Cardinal(Bitmap.Height - 1)))
else
PSource := SourceData.Scan0;
for Scan := 0 to hh - 1 do
begin
if FlipX then
fBltRender(Pointer(Cardinal(PSource) + (Cardinal(SourceData.Width - 1) * 4)), PDest, ww, -4)
else
fBltRender(PSource, PDest, ww, 4);
inc(PDest, DestData.Stride);
if FlipY then
dec(PSource, SourceData.Stride)
else
inc(PSource, SourceData.Stride);
end;
except
end;
Bitmap.Unlock(SourceData);
finally
Unlock(DestData);
end;
end;
procedure TCustomBitmap.RenderAffine;
var
VLDx, VRDx, HDx: Extended;
VLDy, VRDy, HDy: Extended;
TX1, TY1, TX2, TY2: Extended;
tx, ty: Extended;
x, y: Integer;
begin
VLDx := (x3 - x1) / Bitmap.Width;
VRDx := (x4 - x2) / Bitmap.Width;
VLDy := (y3 - y1) / Bitmap.Height;
VRDy := (y4 - y2) / Bitmap.Height;
tx1 := x1; ty1 := y1;
tx2 := x2; ty2 := y2;
for y := 0 to Bitmap.Height - 1 do
begin
HDx := (tx2 - tx1) / Bitmap.Width;
HDy := (ty2 - ty1) / Bitmap.Height;
tx := TX1;
ty := TY1;
for x := 0 to Bitmap.Width - 1 do
begin
DrawPixel(tx, ty, Bitmap.GetPixel(x, y));
tx := tx + HDx;
ty := ty + HDy;
end;
TX1 := TX1 + VLDx; TY1 := TY1 + VLDy;
TX2 := TX2 + VRDx; TY2 := TY2 + VRDy;
end;
end;
procedure TCustomBitmap.RenderAngle;
var
x1, y1: Integer;
x2, y2: Integer;
x3, y3: Integer;
x4, y4: Integer;
cx, cy: Single;
dx, dy: Single;
s, c: Single;
begin
s := sin(Angle - PI);
c := cos(Angle - PI);
cx := x + (Integer(Bitmap.Width) shr 1);
cy := y + (Integer(Bitmap.Height) shr 1);
dx := cx - x;
dy := cy - y;
x1 := x + round(dx * c - dy * s);
y1 := y + round(dx * s + dy * c);
dx := cx - (x + Integer(Bitmap.Width));
dy := cy - y;
x2 := x + round(dx * c - dy * s);
y2 := y + round(dx * s + dy * c);
dx := cx - x;
dy := cy - (y + Integer(Bitmap.Height));
x3 := x + round(dx * c - dy * s);
y3 := y + round(dx * s + dy * c);
dx := cx - (x + Integer(Bitmap.Width));
dy := cy - (y + Integer(Bitmap.Height));
x4 := x + round(dx * c - dy * s);
y4 := y + round(dx * s + dy * c);
RenderAffine(Bitmap, x1, y1, x2, y2, x3, y3, x4, y4);
end;
procedure TCustomBitmap.SwapRB;
type
TARGB = array[0..3] of Byte;
var
x, y: Integer;
p: ^TARGB;
t: Byte;
begin
for y := 0 to Height - 1 do
begin
p := Scanlines[y];
for x := 0 to Width - 1 do
begin
t := p^[0];
p^[0] := p^[2];
p^[2] := t;
p := Pointer(Cardinal(p) + 4);
end;
end;
end;
procedure TCustomBitmap.SetTransparentColour;
var
x, y: Integer;
c: Cardinal;
p: ^Cardinal;
begin
c := Colour and $FFFFFF;
for y := 0 to Height - 1 do
begin
p := Scanlines[y];
for x := 0 to Width - 1 do
begin
if (p^ and $FFFFFF) = c then
p^ := c
else
p^ := $FF000000 or p^;
inc(p);
end;
end;
end;
function TCustomBitmap.LoadFromStream;
var
SavePos: Int64;
Image: TGDIPlusObject;
w, h: Cardinal;
ABitmapData: TBitmapData;
Box: TBox;
begin
Result := False;
if not InitializeGDIPlus then exit;
SavePos := Stream.Position;
Image := nil;
try
if GdipLoadImageFromStream(Stream as IStream, Image) <> 0 then exit(False);
try
GdipGetImageWidth (Image, w);
GdipGetImageHeight(Image, h);
if not Resize(w, h) then exit(False);
Box.x := 0; Box.y := 0;
Box.w := w; Box.h := h;
ABitmapData.Width := w;
ABitmapData.Height := h;
ABitmapData.Stride := BitmapData.Stride;
ABitmapData.Scan0 := BitmapData.Scan0;
ABitmapData.PixelFormat := PixelFormat;
if GdipBitmapLockBits(Image, Box, 5, PixelFormat, ABitmapData) <> 0then exit(False);
GdipBitmapUnlockBits(Image, ABitmapData);
finally
GdipDisposeImage(Image);
end;
Result := True;
finally
if not Result then Stream.Position := SavePos;
end;
end;
function TCustomBitmap.SaveToStream;
var
SavePos: Int64;
Image: TGDIPlusObject;
Encoder: TGUID;
Params: TEncoderParameters;
Num: LongWord;
Size: LongWord;
i: Integer;
Codec: TImageCodecInfos;
QParam: LongWord;
begin
Result := False;
if not InitializeGDIPlus then exit;
GdipGetImageEncodersSize(Num, Size);
if GDIPGetImageEncoders(Num, Size, @Codec) <> 0 then exit(False);
// Search the encoders for a mime match
QParam := $FFFFFFFF;
for i := 0 to Num - 1 do
if String(Codec[i].MimeType) = Mime then
begin
Encoder := Codec[i].CLSID;
QParam := i;
break;
end;
if QParam = $FFFFFFFF then exit(False);
SetLength(Params.Param, 1);
if Quality <> -1 then
begin
QParam := Clamp(Quality, 0, 100);
SetLength(Params.Param, 1);
with Params, Param[0] do
begin
Count := 1;
NumberOfValues := 1;
ParamType := 4;
Value := @QParam;
end;
end;
SavePos := Stream.Position;
try
if GdipCreateBitmapFromScan0(Width, Height, BitmapData.Stride, PixelFormat, BitmapData.Scan0, Image) <> 0 then exit(False);
try
if Quality <> -1 then
Result := GdipSaveImageToStream(Image, Stream as IStream, Encoder, @Params) = 0
else
Result := GdipSaveImageToStream(Image, Stream as IStream, Encoder, nil) = 0;
finally
GdipDisposeImage(Image);
end;
finally
if not Result then Stream.Position := SavePos;
end;
end;
function TCustomBitmap.LoadFromFile;
var
Image: TGDIPlusObject;
w, h: Cardinal;
ABitmapData: TBitmapData;
Box: TBox;
begin
Result := False;
if not InitializeGDIPlus then exit;
Image := nil;
if GdipLoadImageFromFile(@FileName[1], Image) <> 0 then exit(False);
try
GdipGetImageWidth (Image, w);
GdipGetImageHeight(Image, h);
if not Resize(w, h) then exit(False);
Box.x := 0; Box.y := 0;
Box.w := w; Box.h := h;
ABitmapData.Width := w;
ABitmapData.Height := h;
ABitmapData.Stride := BitmapData.Stride;
ABitmapData.Scan0 := BitmapData.Scan0;
ABitmapData.PixelFormat := PixelFormat;
if GdipBitmapLockBits(Image, Box, 5, PixelFormat, ABitmapData) <> 0then exit(False);
GdipBitmapUnlockBits(Image, ABitmapData);
finally
GdipDisposeImage(Image);
end;
Result := True;
end;
function TCustomBitmap.SaveToFile;
var
Image: TGDIPlusObject;
Encoder: TGUID;
Params: TEncoderParameters;
Num: LongWord;
Size: LongWord;
i: Integer;
Codec: TImageCodecInfos;
QParam: LongWord;
begin
Result := False;
if not InitializeGDIPlus then exit;
GdipGetImageEncodersSize(Num, Size);
if GDIPGetImageEncoders(Num, Size, @Codec) <> 0 then exit(False);
// Search the encoders for a mime match
QParam := $FFFFFFFF;
for i := 0 to Num - 1 do
if String(Codec[i].MimeType) = Mime then
begin
Encoder := Codec[i].CLSID;
QParam := i;
break;
end;
if QParam = $FFFFFFFF then exit(False);
SetLength(Params.Param, 1);
if Quality <> -1 then
begin
QParam := Clamp(Quality, 0, 100);
SetLength(Params.Param, 1);
with Params, Param[0] do
begin
Count := 1;
NumberOfValues := 1;
ParamType := 4;
Value := @QParam;
end;
end;
if GdipCreateBitmapFromScan0(Width, Height, BitmapData.Stride, PixelFormat, BitmapData.Scan0, Image) <> 0 then exit(False);
try
if Quality <> -1 then
Result := GdipSaveImageToFile(Image, @FileName[1], Encoder, @Params) = 0
else
Result := GdipSaveImageToFile(Image, @FileName[1], Encoder, nil) = 0;
finally
GdipDisposeImage(Image);
end;
end;
{$ENDREGION}
{$REGION 'TSubBitmap'}
constructor TSubBitmap.Create;
begin
inherited Create;
FillChar(BitmapData, sizeof(BitmapData), 0);
if AParent.Lock(BitmapData, x, y, w, h, [lmRead, lmWrite]) then
begin
fParent := AParent;
Resize(BitmapData.Width, BitmapData.Height);
end
else fParent := nil;
end;
destructor TSubBitmap.Destroy;
begin
if fParent <> nil then Parent.Unlock(BitmapData);
inherited;
end;
{$ENDREGION}
{$REGION 'TBitmap'}
function TBitmap.Resize;
begin
if BitmapData.Scan0 <> nil then FreeMem(BitmapData.Scan0);
FillChar(BitmapData, sizeof(BitmapData), 0);
if (AWidth <> 0) and (AHeight <> 0) then
begin
GetMem(BitmapData.Scan0, (AWidth * 4) * AHeight);
if BitmapData.Scan0 = nil then
begin
inherited Resize(0, 0);
exit(False);
end;
BitmapData.Stride := AWidth * 4;
BitmapData.LockMode := [lmRead, lmWrite];
end;
Result := inherited Resize(AWidth, AHeight);
end;
{$ENDREGION}
{$REGION 'TDIB'}
function TDIB.GetDC;
begin
if fDC = 0 then CreateDC;
Result := fDC;
end;
function TDIB.Resize;
begin
if fHandle <> 0 then DeleteObject(fHandle);
fHandle := 0;
FillChar(BitmapData, sizeof(BitmapData), 0);
if (AWidth <> 0) and (AHeight <> 0) then
begin
with fHeader do
begin
biSize := sizeof(fHeader);
biWidth := AWidth;
biHeight := -AHeight;
biPlanes := 1;
biBitCount := 32;
biCompression := BI_RGB;
end;
fInfo.bmiHeader := fHeader;
fHandle := CreateDIBSection(0, fInfo, DIB_RGB_COLORS, BitmapData.Scan0, 0, 0);
if fHandle = 0 then
begin
inherited Resize(0, 0);
exit(False);
end;
BitmapData.Stride := AWidth * 4;
BitmapData.LockMode := [lmRead, lmWrite];
end;
Result := inherited Resize(AWidth, AHeight);
end;
procedure TDIB.BltToDC;
begin
if (w = 0) or (h = 0) then
SetDIBitsToDevice(DC, x, y, BitmapData.Width, BitmapData.Height, 0, 0, 0, BitmapData.Height, BitmapData.Scan0, fInfo, DIB_RGB_COLORS)
else
StretchDIBits(DC, x, y, w, h, 0, 0, BitmapData.Width, BitmapData.Height, BitmapData.Scan0, fInfo, DIB_RGB_COLORS, SRCCOPY);
end;
procedure TDIB.CreateDC;
var
ScreenDC: HDC;
begin
if fDC <> 0 then Exit;
ScreenDC := Winapi.Windows.GetDC(0);
fDC := CreateCompatibleDC(ScreenDC);
SelectObject(fDC, fHandle);
Winapi.Windows.ReleaseDC(0, ScreenDC);
end;
procedure TDIB.ReleaseDC;
begin
if fDC = 0 then Exit;
Winapi.Windows.ReleaseDC(0, fDC);
fDC := 0;
end;
{$ENDREGION}
{$REGION 'MMX'}
var
_MMXAlphaTable: Pointer = nil;
_MMXBiasPtr: Pointer = nil;
_MMXAlphaPtr: Pointer = nil;
procedure InitMMX;
var
I: Integer;
L: LongWord;
P: ^LongWord;
begin
GetMem(_MMXAlphaTable, 257 * 8);
_MMXAlphaPtr := Pointer(Cardinal(_MMXAlphaTable) and $FFFFFFF8);
if Cardinal(_MMXAlphaPtr) < Cardinal(_MMXAlphaTable) then _MMXAlphaPtr := Pointer(Cardinal(_MMXAlphaPtr) + 8);
P := _MMXAlphaPtr;
for i := 0 to 255 do
begin
l := i + i shl 16;
p^ := l; inc(p);
p^ := l; inc(p);
end;
_MMXBiasPtr := Pointer(Cardinal(_MMXAlphaPtr) + $80 * 8);
end;
procedure DoneMMX;
begin
FreeMem(_MMXAlphaTable);
end;
procedure BltAlphaMMX;
asm
test ecx, ecx
js @4
push esi
push edi
mov esi, eax
mov edi, edx
mov edx, _MMXBiasPtr
@1:
mov eax, [esi]
test eax, $FF000000
jz @3
cmp eax, $FF000000
jnc @2
pxor mm3, mm3
movd mm0, eax
movd mm2, [edi]
punpcklbw mm0, mm3
punpcklbw mm2, mm3
movq mm1, mm0
punpckhwd mm1, mm1
psubw mm0, mm2
punpckhdq mm1, mm1
psllw mm2, 8
pmullw mm0, mm1
paddw mm2, [edx]
paddw mm2, mm0
psrlw mm2, 8
packuswb mm2, mm3
movd eax, mm2
@2:
mov [edi], eax
@3:
add esi, SourceStride
add edi, 4
dec ecx
jnz @1
pop edi
pop esi
@4:
end;
procedure BlkAlphaMMX;
begin
// TODO: MMX BlkAlpha
BlkAlpha(Colour, Dest, Count, Stride);
end;
{$ENDREGION}
initialization
//InitMMX;
finalization
//DoneMMX;
end.