You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RnQ/for.RnQ/Graphics32/GR32_VectorMaps.pas

638 lines
19 KiB
Plaintext

unit GR32_VectorMaps;
(* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1 or LGPL 2.1 with linking exception
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Alternatively, the contents of this file may be used under the terms of the
* Free Pascal modified version of the GNU Lesser General Public License
* Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
* of this license are applicable instead of those above.
* Please see the file LICENSE.txt for additional information concerning this
* license.
*
* The Original Code is GR32_VectorMaps
*
* The Initial Developer of the Original Code is
* Michael Hansen
*
* Portions created by the Initial Developer are Copyright (C) 2000-2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mattias Andersson
*
* ***** END LICENSE BLOCK ***** *)
interface
{$I GR32.inc}
uses
{$IFDEF FPC}
{$IFDEF Windows}
Windows,
{$ENDIF}
{$ELSE}
Windows,
{$ENDIF}
Classes, GR32;
type
TFixedVector = TFixedPoint;
PFixedVector = ^TFixedVector;
TFloatVector = TFloatPoint;
PFloatVector = ^TFloatVector;
TArrayOfFixedVector = array of TFixedVector;
PArrayOfFixedVector = ^TArrayOfFixedVector;
TArrayOfFloatVector = array of TFloatVector;
PArrayOfFloatVector = ^TArrayOfFixedVector;
type
TVectorCombineMode = (vcmAdd, vcmReplace, vcmCustom);
TVectorCombineEvent= procedure(F, P: TFixedVector; var B: TFixedVector) of object;
TVectorMap = class(TCustomMap)
private
FVectors: TArrayOfFixedVector;
FOnVectorCombine: TVectorCombineEvent;
FVectorCombineMode: TVectorCombineMode;
function GetVectors: PFixedPointArray;
function GetFixedVector(X,Y: Integer): TFixedVector;
function GetFixedVectorS(X,Y: Integer): TFixedVector;
function GetFixedVectorX(X,Y: TFixed): TFixedVector;
function GetFixedVectorXS(X,Y: TFixed): TFixedVector;
function GetFloatVector(X,Y: Integer): TFloatVector;
function GetFloatVectorS(X,Y: Integer): TFloatVector;
function GetFloatVectorF(X,Y: Single): TFloatVector;
function GetFloatVectorFS(X,Y: Single): TFloatVector;
procedure SetFixedVector(X,Y: Integer; const Point: TFixedVector);
procedure SetFixedVectorS(X,Y: Integer; const Point: TFixedVector);
procedure SetFixedVectorX(X,Y: TFixed; const Point: TFixedVector);
procedure SetFixedVectorXS(X,Y: TFixed; const Point: TFixedVector);
procedure SetFloatVector(X,Y: Integer; const Point: TFloatVector);
procedure SetFloatVectorS(X,Y: Integer; const Point: TFloatVector);
procedure SetFloatVectorF(X,Y: Single; const Point: TFloatVector);
procedure SetFloatVectorFS(X,Y: Single; const Point: TFloatVector);
procedure SetVectorCombineMode(const Value: TVectorCombineMode);
protected
procedure ChangeSize(var Width, Height: Integer; NewWidth,
NewHeight: Integer); override;
public
destructor Destroy; override;
procedure Clear;
procedure Merge(DstLeft, DstTop: Integer; Src: TVectorMap; SrcRect: TRect);
property Vectors: PFixedPointArray read GetVectors;
function BoundsRect: TRect;
function GetTrimmedBounds: TRect;
function Empty: Boolean; override;
procedure LoadFromFile(const FileName: string);
procedure SaveToFile(const FileName: string);
property FixedVector[X, Y: Integer]: TFixedVector read GetFixedVector write SetFixedVector; default;
property FixedVectorS[X, Y: Integer]: TFixedVector read GetFixedVectorS write SetFixedVectorS;
property FixedVectorX[X, Y: TFixed]: TFixedVector read GetFixedVectorX write SetFixedVectorX;
property FixedVectorXS[X, Y: TFixed]: TFixedVector read GetFixedVectorXS write SetFixedVectorXS;
property FloatVector[X, Y: Integer]: TFloatVector read GetFloatVector write SetFloatVector;
property FloatVectorS[X, Y: Integer]: TFloatVector read GetFloatVectorS write SetFloatVectorS;
property FloatVectorF[X, Y: Single]: TFloatVector read GetFloatVectorF write SetFloatVectorF;
property FloatVectorFS[X, Y: Single]: TFloatVector read GetFloatVectorFS write SetFloatVectorFS;
published
property VectorCombineMode: TVectorCombineMode read FVectorCombineMode write SetVectorCombineMode;
property OnVectorCombine: TVectorCombineEvent read FOnVectorCombine write FOnVectorCombine;
end;
implementation
uses
GR32_Lowlevel, GR32_Blend, GR32_Transforms, GR32_Math, SysUtils;
resourcestring
RCStrCantAllocateVectorMap = 'Can''t allocate VectorMap!';
RCStrBadFormat = 'Bad format - Photoshop .msh expected!';
RCStrFileNotFound = 'File not found!';
RCStrSrcIsEmpty = 'Src is empty!';
RCStrBaseIsEmpty = 'Base is empty!';
{ TVectorMap }
function CombineVectorsReg(const A, B: TFixedVector; Weight: TFixed): TFixedVector;
begin
Result.X := FixedCombine(Weight, B.X, A.X);
Result.Y := FixedCombine(Weight, B.Y, A.Y);
end;
procedure CombineVectorsMem(const A: TFixedVector;var B: TFixedVector; Weight: TFixed);
begin
B.X := FixedCombine(Weight, B.X, A.X);
B.Y := FixedCombine(Weight, B.Y, A.Y);
end;
function TVectorMap.BoundsRect: TRect;
begin
Result := Rect(0, 0, Width, Height);
end;
procedure TVectorMap.ChangeSize(var Width, Height: Integer;
NewWidth, NewHeight: Integer);
begin
inherited;
FVectors := nil;
Width := 0;
Height := 0;
SetLength(FVectors, NewWidth * NewHeight);
if (NewWidth > 0) and (NewHeight > 0) then
begin
if FVectors = nil then raise Exception.Create(RCStrCantAllocateVectorMap);
FillLongword(FVectors[0], NewWidth * NewHeight * 2, 0);
end;
Width := NewWidth;
Height := NewHeight;
end;
procedure TVectorMap.Clear;
begin
FillLongword(FVectors[0], Width * Height * 2, 0);
end;
destructor TVectorMap.Destroy;
begin
Lock;
try
SetSize(0, 0);
finally
Unlock;
end;
inherited;
end;
function TVectorMap.GetVectors: PFixedPointArray;
begin
Result := @FVectors[0];
end;
function TVectorMap.GetFloatVector(X, Y: Integer): TFloatVector;
begin
Result := FloatPoint(FVectors[X + Y * Width]);
end;
function TVectorMap.GetFloatVectorF(X, Y: Single): TFloatVector;
begin
Result := FloatPoint(GetFixedVectorX(Fixed(X), Fixed(Y)));
end;
function TVectorMap.GetFloatVectorFS(X, Y: Single): TFloatVector;
begin
Result := FloatPoint(GetFixedVectorXS(Fixed(X), Fixed(Y)));
end;
function TVectorMap.GetFloatVectorS(X, Y: Integer): TFloatVector;
begin
if (X >= 0) and (Y >= 0) and
(X < Width) and (Y < Height) then
Result := GetFloatVector(X,Y)
else
begin
Result.X := 0;
Result.Y := 0;
end;
end;
function TVectorMap.GetFixedVector(X, Y: Integer): TFixedVector;
begin
Result := FVectors[X + Y * Width];
end;
function TVectorMap.GetFixedVectorS(X, Y: Integer): TFixedVector;
begin
if (X >= 0) and (Y >= 0) and
(X < Width) and (Y < Height) then
Result := GetFixedVector(X,Y)
else
begin
Result.X := 0;
Result.Y := 0;
end;
end;
function TVectorMap.GetFixedVectorX(X, Y: TFixed): TFixedVector;
const
Next = SizeOf(TFixedVector);
var
WX,WY: TFixed;
W, H: Integer;
P: Pointer;
begin
WX := TFixedRec(X).Int;
WY := TFixedRec(Y).Int;
W := Width;
H := Height;
if (WX >= 0) and (WX <= W - 1) and (WY >= 0) and (WY <= H - 1) then
begin
P := @FVectors[WX + WY * W];
if (WY = H - 1) then W := 0 else W := W * Next;
if (WX = W - 1) then H := 0 else H := Next;
WX := TFixedRec(X).Frac;
WY := TFixedRec(Y).Frac;
{$IFDEF HAS_NATIVEINT}
Result := CombineVectorsReg(CombineVectorsReg(PFixedPoint(P)^,
PFixedPoint(NativeUInt(P) + NativeUInt(H))^, WX), CombineVectorsReg(
PFixedPoint(NativeUInt(P) + NativeUInt(W))^, PFixedPoint(
NativeUInt(P) + NativeUInt(W) + NativeUInt(H))^, WX), WY);
{$ELSE}
Result := CombineVectorsReg(CombineVectorsReg(PFixedPoint(P)^,
PFixedPoint(Cardinal(P) + H)^, WX), CombineVectorsReg(
PFixedPoint(Cardinal(P) + W)^, PFixedPoint(Cardinal(P) + W + H)^, WX),
WY);
{$ENDIF}
end else
begin
Result.X := 0;
Result.Y := 0;
end;
end;
function TVectorMap.GetFixedVectorXS(X, Y: TFixed): TFixedVector;
var
WX,WY: TFixed;
begin
WX := TFixedRec(X).Frac;
X := TFixedRec(X).Int;
WY := TFixedRec(Y).Frac;
Y := TFixedRec(Y).Int;
Result := CombineVectorsReg(CombineVectorsReg(FixedVectorS[X,Y], FixedVectorS[X + 1,Y], WX),
CombineVectorsReg(FixedVectorS[X,Y + 1], FixedVectorS[X + 1,Y + 1], WX), WY);
end;
function TVectorMap.Empty: Boolean;
begin
Result := false;
if (Width = 0) or (Height = 0) or (FVectors = nil) then Result := True;
end;
const
MeshIdent = 'yfqLhseM';
type
{TVectorMap supports the photoshop liquify mesh fileformat .msh}
TPSLiquifyMeshHeader = record
Pad0 : dword;
Ident : array [0..7] of Char;
Pad1 : dword;
Width : dword;
Height: dword;
end;
procedure TVectorMap.LoadFromFile(const FileName: string);
procedure ConvertVertices;
var
I: Integer;
begin
for I := 0 to Length(FVectors) - 1 do
begin
//Not a mistake! Converting physical mem. directly to avoid temporary floating point buffer
//Do no change to PFloat.. the type is relative to the msh format.
FVectors[I].X := Fixed(PSingle(@FVectors[I].X)^);
FVectors[I].Y := Fixed(PSingle(@FVectors[I].Y)^);
end;
end;
var
Header: TPSLiquifyMeshHeader;
MeshFile: File;
begin
If FileExists(Filename) then
try
AssignFile(MeshFile, FileName);
Reset(MeshFile, 1);
BlockRead(MeshFile, Header, SizeOf(TPSLiquifyMeshHeader));
if Lowercase(String(Header.Ident)) <> Lowercase(MeshIdent) then
Exception.Create(RCStrBadFormat);
with Header do
begin
SetSize(Width, Height);
BlockRead(MeshFile, FVectors[0], Width * Height * SizeOf(TFixedVector));
ConvertVertices;
end;
finally
CloseFile(MeshFile);
end
else Exception.Create(RCStrFileNotFound);
end;
procedure TVectorMap.Merge(DstLeft, DstTop: Integer; Src: TVectorMap; SrcRect: TRect);
var
I,J,P: Integer;
DstRect: TRect;
Progression: TFixedVector;
ProgressionX, ProgressionY: TFixed;
CombineCallback: TVectorCombineEvent;
DstPtr : PFixedPointArray;
SrcPtr : PFixedPoint;
begin
if Src.Empty then Exception.Create(RCStrSrcIsEmpty);
if Empty then Exception.Create(RCStrBaseIsEmpty);
IntersectRect( SrcRect, Src.BoundsRect, SrcRect);
DstRect.Left := DstLeft;
DstRect.Top := DstTop;
DstRect.Right := DstLeft + (SrcRect.Right - SrcRect.Left);
DstRect.Bottom := DstTop + (SrcRect.Bottom - SrcRect.Top);
IntersectRect(DstRect, BoundsRect, DstRect);
if IsRectEmpty(DstRect) then Exit;
P := SrcRect.Top * Src.Width;
Progression.Y := - FixedOne;
case Src.FVectorCombineMode of
vcmAdd:
begin
for I := DstRect.Top to DstRect.Bottom do
begin
// Added ^ for FPC
DstPtr := @GetVectors^[I * Width];
SrcPtr := @Src.GetVectors^[SrcRect.Left + P];
for J := DstRect.Left to DstRect.Right do
begin
Inc(SrcPtr^.X, DstPtr[J].X);
Inc(SrcPtr^.Y, DstPtr[J].Y);
Inc(SrcPtr);
end;
Inc(P, Src.Width);
end;
end;
vcmReplace:
begin
for I := DstRect.Top to DstRect.Bottom do
begin
// Added ^ for FPC
DstPtr := @GetVectors^[I * Width];
SrcPtr := @Src.GetVectors^[SrcRect.Left + P];
for J := DstRect.Left to DstRect.Right do
begin
SrcPtr^.X := DstPtr[J].X;
SrcPtr^.Y := DstPtr[J].Y;
Inc(SrcPtr);
end;
Inc(P, Src.Width);
end;
end;
else
CombineCallback := Src.FOnVectorCombine;
ProgressionX := Fixed(2 / (DstRect.Right - DstRect.Left - 1));
ProgressionY := Fixed(2 / (DstRect.Bottom - DstRect.Top - 1));
for I := DstRect.Top to DstRect.Bottom do
begin
Progression.X := - FixedOne;
// Added ^ for FPC
DstPtr := @GetVectors^[I * Width];
SrcPtr := @Src.GetVectors^[SrcRect.Left + P];
for J := DstRect.Left to DstRect.Right do
begin
CombineCallback(SrcPtr^, Progression, DstPtr[J]);
Inc(SrcPtr);
Inc(Progression.X, ProgressionX);
end;
Inc(P, Src.Width);
Inc(Progression.Y, ProgressionY);
end;
end;
end;
procedure TVectorMap.SaveToFile(const FileName: string);
procedure ConvertVerticesX;
var
I: Integer;
begin
for I := 0 to Length(FVectors) - 1 do
begin
//Not a mistake! Converting physical mem. directly to avoid temporary floating point buffer
//Do no change to PFloat.. the type is relative to the msh format.
FVectors[I].X := Fixed(PSingle(@FVectors[I].X)^);
FVectors[I].Y := Fixed(PSingle(@FVectors[I].Y)^);
end;
end;
procedure ConvertVerticesF;
var
I: Integer;
begin
for I := 0 to Length(FVectors) - 1 do
begin
//Not a mistake! Converting physical mem. directly to avoid temporary floating point buffer
//Do no change to PFloat.. the type is relative to the msh format.
PSingle(@FVectors[I].X)^ := FVectors[I].X * FixedToFloat;
PSingle(@FVectors[I].Y)^ := FVectors[I].Y * FixedToFloat;
end;
end;
var
Header: TPSLiquifyMeshHeader;
MeshFile: File;
Pad: Cardinal;
begin
try
AssignFile(MeshFile, FileName);
Rewrite(MeshFile, 1);
with Header do
begin
Pad0 := $02000000;
Ident := MeshIdent;
Pad1 := $00000002;
Width := Self.Width;
Height := Self.Height;
end;
BlockWrite(MeshFile, Header, SizeOf(TPSLiquifyMeshHeader));
with Header do
begin
ConvertVerticesF;
BlockWrite(MeshFile, FVectors[0], Length(FVectors) * SizeOf(TFixedVector));
ConvertVerticesX;
end;
if Odd(Length(FVectors) * SizeOf(TFixedVector) - 1) then
begin
Pad := $00000000;
BlockWrite(MeshFile, Pad, 4);
BlockWrite(MeshFile, Pad, 4);
end;
finally
CloseFile(MeshFile);
end;
end;
procedure TVectorMap.SetFloatVector(X, Y: Integer; const Point: TFloatVector);
begin
FVectors[X + Y * Width] := FixedPoint(Point);
end;
procedure TVectorMap.SetFloatVectorF(X, Y: Single; const Point: TFloatVector);
begin
SetFixedVectorX(Fixed(X), Fixed(Y), FixedPoint(Point));
end;
procedure TVectorMap.SetFloatVectorFS(X, Y: Single; const Point: TFloatVector);
begin
SetFixedVectorXS(Fixed(X), Fixed(Y), FixedPoint(Point));
end;
procedure TVectorMap.SetFloatVectorS(X, Y: Integer; const Point: TFloatVector);
begin
if (X >= 0) and (X < Width) and
(Y >= 0) and (Y < Height) then
FVectors[X + Y * Width] := FixedPoint(Point);
end;
procedure TVectorMap.SetFixedVector(X, Y: Integer; const Point: TFixedVector);
begin
FVectors[X + Y * Width] := Point;
end;
procedure TVectorMap.SetFixedVectorS(X, Y: Integer; const Point: TFixedVector);
begin
if (X >= 0) and (X < Width) and
(Y >= 0) and (Y < Height) then
FVectors[X + Y * Width] := Point;
end;
procedure TVectorMap.SetFixedVectorX(X, Y: TFixed; const Point: TFixedVector);
var
flrx, flry, celx, cely: Integer;
P: PFixedPoint;
begin
flrx := TFixedRec(X).Frac;
celx := flrx xor $FFFF;
flry := TFixedRec(Y).Frac;
cely := flry xor $FFFF;
P := @FVectors[TFixedRec(X).Int + TFixedRec(Y).Int * Width];
CombineVectorsMem(Point, P^, FixedMul(celx, cely)); Inc(P);
CombineVectorsMem(Point, P^, FixedMul(flrx, cely)); Inc(P, Width);
CombineVectorsMem(Point, P^, FixedMul(flrx, flry)); Dec(P);
CombineVectorsMem(Point, P^, FixedMul(celx, flry));
end;
procedure TVectorMap.SetFixedVectorXS(X, Y: TFixed; const Point: TFixedVector);
var
flrx, flry, celx, cely: Integer;
P: PFixedPoint;
begin
if (X < -$10000) or (Y < -$10000) then Exit;
flrx := TFixedRec(X).Frac;
X := TFixedRec(X).Int;
flry := TFixedRec(Y).Frac;
Y := TFixedRec(Y).Int;
if (X >= Width) or (Y >= Height) then Exit;
celx := flrx xor $FFFF;
cely := flry xor $FFFF;
P := @FVectors[X + Y * Width];
if (X >= 0) and (Y >= 0)then
begin
CombineVectorsMem(Point, P^, FixedMul(celx, cely) ); Inc(P);
CombineVectorsMem(Point, P^, FixedMul(flrx, cely) ); Inc(P, Width);
CombineVectorsMem(Point, P^, FixedMul(flrx, flry) ); Dec(P);
CombineVectorsMem(Point, P^, FixedMul(celx, flry) );
end
else
begin
if (X >= 0) and (Y >= 0) then CombineVectorsMem(Point, P^, FixedMul(celx, cely)); Inc(P);
if (X < Width - 1) and (Y >= 0) then CombineVectorsMem(Point, P^, FixedMul(flrx, cely)); Inc(P, Width);
if (X < Width - 1) and (Y < Height - 1) then CombineVectorsMem(Point, P^, FixedMul(flrx, flry)); Dec(P);
if (X >= 0) and (Y < Height - 1) then CombineVectorsMem(Point, P^, FixedMul(celx, flry));
end;
end;
procedure TVectorMap.SetVectorCombineMode(const Value: TVectorCombineMode);
begin
if FVectorCombineMode <> Value then
begin
FVectorCombineMode := Value;
Changed;
end;
end;
function TVectorMap.GetTrimmedBounds: TRect;
var
J: Integer;
VectorPtr : PFixedVector;
label
TopDone, BottomDone, LeftDone, RightDone;
begin
with Result do
begin
//Find Top
Top := 0;
VectorPtr := @Vectors[Top];
repeat
if Int64(VectorPtr^) <> 0 then goto TopDone;
Inc(VectorPtr);
Inc(Top);
until Top = Self.Width * Self.Height;
TopDone: Top := Top div Self.Width;
//Find Bottom
Bottom := Self.Width * Self.Height - 1;
VectorPtr := @Vectors[Bottom];
repeat
if Int64(VectorPtr^) <> 0 then goto BottomDone;
Dec(VectorPtr);
Dec(Bottom);
until Bottom < 0;
BottomDone: Bottom := Bottom div Self.Width - 1;
//Find Left
Left := 0;
repeat
J := Top;
repeat
if Int64(FixedVector[Left, J]) <> 0 then goto LeftDone;
Inc(J);
until J >= Bottom;
Inc(Left)
until Left >= Self.Width;
LeftDone:
//Find Right
Right := Self.Width - 1;
repeat
J := Bottom;
repeat
if Int64(FixedVector[Right, J]) <> 0 then goto RightDone;
Dec(J);
until J <= Top;
Dec(Right)
until Right <= Left;
end;
RightDone:
if IsRectEmpty(Result) then
Result := Rect(0,0,0,0);
end;
end.