{----------------------------------------------------------------------------- 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/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: JvGraph.PAS, released on 2002-07-04. The Initial Developers of the Original Code are: Fedor Koshevnikov, Igor Pavluk and Serge Korolev Copyright (c) 1997, 1998 Fedor Koshevnikov, Igor Pavluk and Serge Korolev Copyright (c) 2001,2002 SGB Software All Rights Reserved. Last Modified: 2002-07-04 You may retrieve the latest version of this file at the Project JEDI's JVCL home page, located at http://jvcl.sourceforge.net Known Issues: -----------------------------------------------------------------------------} { WARNING: source contains a bugfix in procedure BitmapToMemoryStream: ImageSize was not adjusted to PixelFormat when going from pf24Bit to pf8Bit } {$I JVCL.INC} unit JvGraph; interface uses {$IFDEF WIN32} Windows, {$ELSE} WinTypes, WinProcs, {$ENDIF} SysUtils, Classes, Graphics, JvVCLUtils; type {$IFNDEF COMPILER3_UP} TPixelFormat = (pfDevice, pf1bit, pf4bit, pf8bit, pf24bit); {$ENDIF} TMappingMethod = (mmHistogram, mmQuantize, mmTrunc784, mmTrunc666, mmTripel, mmGrayscale); function GetBitmapPixelFormat(Bitmap: TBitmap): TPixelFormat; function GetPaletteBitmapFormat(Bitmap: TBitmap): TPixelFormat; procedure SetBitmapPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat; Method: TMappingMethod); function BitmapToMemoryStream(Bitmap: TBitmap; PixelFormat: TPixelFormat; Method: TMappingMethod): TMemoryStream; procedure GrayscaleBitmap(Bitmap: TBitmap); function BitmapToMemory(Bitmap: TBitmap; Colors: Integer): TStream; procedure SaveBitmapToFile(const Filename: string; Bitmap: TBitmap; Colors: Integer); function ScreenPixelFormat: TPixelFormat; function ScreenColorCount: Integer; procedure TileImage(Canvas: TCanvas; Rect: TRect; Image: TGraphic); function ZoomImage(ImageW, ImageH, MaxW, MaxH: Integer; Stretch: Boolean): TPoint; var DefaultMappingMethod: TMappingMethod = mmHistogram; type TJvGradient = class(TPersistent) private FStartColor: TColor; FEndColor: TColor; FDirection: TFillDirection; FStepCount: Byte; FVisible: Boolean; FOnChange: TNotifyEvent; procedure SetStartColor(Value: TColor); procedure SetEndColor(Value: TColor); procedure SetDirection(Value: TFillDirection); procedure SetStepCount(Value: Byte); procedure SetVisible(Value: Boolean); protected procedure Changed; dynamic; public constructor Create; procedure Assign(Source: TPersistent); override; procedure Draw(Canvas: TCanvas; Rect: TRect); published property Direction: TFillDirection read FDirection write SetDirection default fdTopToBottom; property EndColor: TColor read FEndColor write SetEndColor default clGray; property StartColor: TColor read FStartColor write SetStartColor default clSilver; property StepCount: Byte read FStepCount write SetStepCount default 64; property Visible: Boolean read FVisible write SetVisible default False; property OnChange: TNotifyEvent read FOnChange write FOnChange; end; implementation {$R-} uses Consts; // (rom) moved here to make JvMaxMin obsolete function MaxFloat(const Values: array of Extended): Extended; var I: Cardinal; begin Result := Values[Low(Values)]; for I := Low(Values)+1 to High(Values) do if Values[I] > Result then Result := Values[I]; end; procedure InvalidBitmap; near; begin raise EInvalidGraphic.Create(ResStr(SInvalidBitmap)); end; type PRGBPalette = ^TRGBPalette; TRGBPalette = array [Byte] of TRGBQuad; function WidthBytes(I: Longint): Longint; begin Result := ((I + 31) div 32) * 4; end; function PixelFormatToColors(PixelFormat: TPixelFormat): Integer; begin case PixelFormat of pf1bit: Result := 2; pf4bit: Result := 16; pf8bit: Result := 256; else Result := 0; end; end; function ScreenPixelFormat: TPixelFormat; var DC: HDC; begin DC := GetDC(0); try case (GetDeviceCaps(DC, PLANES) * GetDeviceCaps(DC, BITSPIXEL)) of 1: Result := pf1bit; 4: Result := pf4bit; 8: Result := pf8bit; 24: Result := pf24bit; else Result := pfDevice; end; finally ReleaseDC(0, DC); end; end; function ScreenColorCount: Integer; begin Result := PixelFormatToColors(ScreenPixelFormat); end; { Quantizing } { Quantizing procedures based on free C source code written by Joe C. Oliphant, CompuServe 71742, 1451, joe_oliphant@csufresno.edu } const MAX_COLORS = 4096; type PQColor = ^TQColor; TQColor = record RGB: array [0..2] of Byte; NewColorIndex: Byte; Count: Longint; PNext: PQColor; end; PQColorArray = ^TQColorArray; TQColorArray = array [0..MAX_COLORS - 1] of TQColor; PQColorList = ^TQColorList; TQColorList = array [0..MaxListSize - 1] of PQColor; PNewColor = ^TNewColor; TNewColor = record RGBMin, RGBWidth: array [0..2] of Byte; NumEntries: Longint; Count: Longint; QuantizedColors: PQColor; end; PNewColorArray = ^TNewColorArray; TNewColorArray = array[Byte] of TNewColor; procedure PInsert(ColorList: PQColorList; Number: Integer; SortRGBAxis: Integer); var Q1, Q2: PQColor; I, J: Integer; Temp: PQColor; begin for I := 1 to Number - 1 do begin Temp := ColorList^[I]; J := I - 1; while J >= 0 do begin Q1 := Temp; Q2 := ColorList^[J]; if Q1^.RGB[SortRGBAxis] - Q2^.RGB[SortRGBAxis] > 0 then Break; ColorList^[J + 1] := ColorList^[J]; Dec(J); end; ColorList^[J + 1] := Temp; end; end; procedure PSort(ColorList: PQColorList; Number: Integer; SortRGBAxis: Integer); var Q1, Q2: PQColor; I, J, N, Nr: Integer; Temp, Part: PQColor; begin if Number < 8 then begin PInsert(ColorList, Number, SortRGBAxis); Exit; end; Part := ColorList^[Number div 2]; I := -1; J := Number; repeat repeat Inc(I); Q1 := ColorList^[I]; Q2 := Part; N := Q1^.RGB[SortRGBAxis] - Q2^.RGB[SortRGBAxis]; until N >= 0; repeat Dec(J); Q1 := ColorList^[J]; Q2 := Part; N := Q1^.RGB[SortRGBAxis] - Q2^.RGB[SortRGBAxis]; until N <= 0; if I >= J then Break; Temp := ColorList^[I]; ColorList^[I] := ColorList^[J]; ColorList^[J] := Temp; until False; Nr := Number - I; if I < Number div 2 then begin PSort(ColorList, I, SortRGBAxis); PSort(PQColorList(@ColorList^[I]), Nr, SortRGBAxis); end else begin PSort(PQColorList(@ColorList^[I]), Nr, SortRGBAxis); PSort(ColorList, I, SortRGBAxis); end; end; function DivideMap(NewColorSubdiv: PNewColorArray; ColorMapSize: Integer; var NewColormapSize: Integer; lpStr: Pointer): Integer; var I, J: {$IFDEF WIN32} Integer {$ELSE}Cardinal {$ENDIF}; MaxSize, Index: Integer; NumEntries, MinColor, MaxColor: {$IFDEF WIN32} Integer {$ELSE} Cardinal {$ENDIF}; Sum, Count: Longint; QuantizedColor: PQColor; SortArray: PQColorList; SortRGBAxis: Integer; begin Index := 0; SortRGBAxis := 0; while ColorMapSize > NewColormapSize do begin MaxSize := -1; for I := 0 to NewColormapSize - 1 do begin for J := 0 to 2 do begin if (NewColorSubdiv^[I].RGBwidth[J] > MaxSize) and (NewColorSubdiv^[I].NumEntries > 1) then begin MaxSize := NewColorSubdiv^[I].RGBwidth[J]; Index := I; SortRGBAxis := J; end; end; end; if MaxSize = -1 then begin Result := 1; Exit; end; SortArray := PQColorList(lpStr); J := 0; QuantizedColor := NewColorSubdiv^[Index].QuantizedColors; while (J < NewColorSubdiv^[Index].NumEntries) and (QuantizedColor <> nil) do begin SortArray^[J] := QuantizedColor; Inc(J); QuantizedColor := QuantizedColor^.pnext; end; PSort(SortArray, NewColorSubdiv^[Index].NumEntries, SortRGBAxis); for J := 0 to NewColorSubdiv^[Index].NumEntries - 2 do SortArray^[J]^.pnext := SortArray^[J + 1]; SortArray^[NewColorSubdiv^[Index].NumEntries - 1]^.pnext := nil; NewColorSubdiv^[Index].QuantizedColors := SortArray^[0]; QuantizedColor := SortArray^[0]; Sum := NewColorSubdiv^[Index].Count div 2 - QuantizedColor^.Count; NumEntries := 1; Count := QuantizedColor^.Count; Dec(Sum, QuantizedColor^.pnext^.Count); while (Sum >= 0) and (QuantizedColor^.pnext <> nil) and (QuantizedColor^.pnext^.pnext <> nil) do begin QuantizedColor := QuantizedColor^.pnext; Inc(NumEntries); Inc(Count, QuantizedColor^.Count); Dec(Sum, QuantizedColor^.pnext^.Count); end; MaxColor := (QuantizedColor^.RGB[SortRGBAxis]) shl 4; MinColor := (QuantizedColor^.pnext^.RGB[SortRGBAxis]) shl 4; NewColorSubdiv^[NewColormapSize].QuantizedColors := QuantizedColor^.pnext; QuantizedColor^.pnext := nil; NewColorSubdiv^[NewColormapSize].Count := Count; Dec(NewColorSubdiv^[Index].Count, Count); NewColorSubdiv^[NewColormapSize].NumEntries := NewColorSubdiv^[Index].NumEntries - NumEntries; NewColorSubdiv^[Index].NumEntries := NumEntries; for J := 0 to 2 do begin NewColorSubdiv^[NewColormapSize].RGBmin[J] := NewColorSubdiv^[Index].RGBmin[J]; NewColorSubdiv^[NewColormapSize].RGBwidth[J] := NewColorSubdiv^[Index].RGBwidth[J]; end; NewColorSubdiv^[NewColormapSize].RGBwidth[SortRGBAxis] := NewColorSubdiv^[NewColormapSize].RGBmin[SortRGBAxis] + NewColorSubdiv^[NewColormapSize].RGBwidth[SortRGBAxis] - MinColor; NewColorSubdiv^[NewColormapSize].RGBmin[SortRGBAxis] := MinColor; NewColorSubdiv^[Index].RGBwidth[SortRGBAxis] := MaxColor - NewColorSubdiv^[Index].RGBmin[SortRGBAxis]; Inc(NewColormapSize); end; Result := 1; end; function Quantize(const bmp: TBitmapInfoHeader; gptr, Data8: Pointer; var ColorCount: Integer; var OutputColormap: TRGBPalette): Integer; type PWord = ^Word; var P: PByteArray; LineBuffer, Data: Pointer; LineWidth: Longint; TmpLineWidth, NewLineWidth: Longint; I, J: Longint; Index: Word; NewColormapSize, NumOfEntries: Integer; Mems: Longint; cRed, cGreen, cBlue: Longint; lpStr, Temp, Tmp: Pointer; NewColorSubdiv: PNewColorArray; ColorArrayEntries: PQColorArray; QuantizedColor: PQColor; begin LineWidth := WidthBytes(Longint(bmp.biWidth) * bmp.biBitCount); Mems := (Longint(SizeOf(TQColor)) * (MAX_COLORS)) + (Longint(SizeOf(TNewColor)) * 256) + LineWidth + (Longint(sizeof(PQCOLOR)) * (MAX_COLORS)); lpStr := AllocMemo(Mems); try Temp := AllocMemo(Longint(bmp.biWidth) * Longint(bmp.biHeight) * SizeOf(Word)); try ColorArrayEntries := PQColorArray(lpStr); NewColorSubdiv := PNewColorArray(HugeOffset(lpStr, Longint(sizeof(TQColor)) * (MAX_COLORS))); LineBuffer := HugeOffset(lpStr, (Longint(sizeof(TQColor)) * (MAX_COLORS)) + (Longint(sizeof(TNewColor)) * 256)); for I := 0 to MAX_COLORS - 1 do begin ColorArrayEntries^[I].RGB[0] := I shr 8; ColorArrayEntries^[I].RGB[1] := (I shr 4) and $0F; ColorArrayEntries^[I].RGB[2] := I and $0F; ColorArrayEntries^[I].Count := 0; end; Tmp := Temp; for I := 0 to bmp.biHeight - 1 do begin HMemCpy(LineBuffer, HugeOffset(gptr, (bmp.biHeight - 1 - I) * LineWidth), LineWidth); P := LineBuffer; for J := 0 to bmp.biWidth - 1 do begin Index := (Longint(P^[2] and $F0) shl 4) + Longint(P^[1] and $F0) + (Longint(P^[0] and $F0) shr 4); Inc(ColorArrayEntries^[Index].Count); P := HugeOffset(P, 3); PWord(Tmp)^ := Index; Tmp := HugeOffset(Tmp, 2); end; end; for I := 0 to 255 do begin NewColorSubdiv^[I].QuantizedColors := nil; NewColorSubdiv^[I].Count := 0; NewColorSubdiv^[I].NumEntries := 0; for J := 0 to 2 do begin NewColorSubdiv^[I].RGBmin[J] := 0; NewColorSubdiv^[I].RGBwidth[J] := 255; end; end; I := 0; while I < MAX_COLORS do begin if ColorArrayEntries^[I].Count > 0 then Break; Inc(I); end; QuantizedColor := @ColorArrayEntries^[I]; NewColorSubdiv^[0].QuantizedColors := @ColorArrayEntries^[I]; NumOfEntries := 1; Inc(I); while I < MAX_COLORS do begin if ColorArrayEntries^[I].Count > 0 then begin QuantizedColor^.pnext := @ColorArrayEntries^[I]; QuantizedColor := @ColorArrayEntries^[I]; Inc(NumOfEntries); end; Inc(I); end; QuantizedColor^.pnext := nil; NewColorSubdiv^[0].NumEntries := NumOfEntries; NewColorSubdiv^[0].Count := Longint(bmp.biWidth) * Longint(bmp.biHeight); NewColormapSize := 1; DivideMap(NewColorSubdiv, ColorCount, NewColormapSize, HugeOffset(lpStr, Longint(SizeOf(TQColor)) * (MAX_COLORS) + Longint(SizeOf(TNewColor)) * 256 + LineWidth)); if NewColormapSize < ColorCount then begin for I := NewColormapSize to ColorCount - 1 do FillChar(OutputColormap[I], SizeOf(TRGBQuad), 0); end; for I := 0 to NewColormapSize - 1 do begin J := NewColorSubdiv^[I].NumEntries; if J > 0 then begin QuantizedColor := NewColorSubdiv^[I].QuantizedColors; cRed := 0; cGreen := 0; cBlue := 0; while QuantizedColor <> nil do begin QuantizedColor^.NewColorIndex := I; Inc(cRed, QuantizedColor^.RGB[0]); Inc(cGreen, QuantizedColor^.RGB[1]); Inc(cBlue, QuantizedColor^.RGB[2]); QuantizedColor := QuantizedColor^.pnext; end; with OutputColormap[I] do begin rgbRed := (Longint(cRed shl 4) or $0F) div J; rgbGreen := (Longint(cGreen shl 4) or $0F) div J; rgbBlue := (Longint(cBlue shl 4) or $0F) div J; rgbReserved := 0; if (rgbRed <= $10) and (rgbGreen <= $10) and (rgbBlue <= $10) then FillChar(OutputColormap[I], SizeOf(TRGBQuad), 0); { clBlack } end; end; end; TmpLineWidth := Longint(bmp.biWidth) * SizeOf(Word); NewLineWidth := WidthBytes(Longint(bmp.biWidth) * 8); FillChar(Data8^, NewLineWidth * bmp.biHeight, #0); for I := 0 to bmp.biHeight - 1 do begin LineBuffer := HugeOffset(Temp, (bmp.biHeight - 1 - I) * TmpLineWidth); Data := HugeOffset(Data8, I * NewLineWidth); for J := 0 to bmp.biWidth - 1 do begin PByte(Data)^ := ColorArrayEntries^[PWord(LineBuffer)^].NewColorIndex; LineBuffer := HugeOffset(LineBuffer, 2); Data := HugeOffset(Data, 1); end; end; finally FreeMemo(Temp); end; finally FreeMemo(lpStr); end; ColorCount := NewColormapSize; Result := 0; end; { Procedures to truncate to lower bits-per-pixel, grayscale, tripel and histogram conversion based on freeware C source code of GBM package by Andy Key (nyangau@interalpha.co.uk). The home page of GBM author is at http://www.interalpha.net/customer/nyangau/. } { Truncate to lower bits per pixel } type TTruncLine = procedure(Src, Dest: Pointer; CX: Integer); { For 6Rx6Gx6B, 7Rx8Gx4B palettes etc. } const Scale04: array [0..3] of Byte = (0, 85, 170, 255); Scale06: array [0..5] of Byte = (0, 51, 102, 153, 204, 255); Scale07: array [0..6] of Byte = (0, 43, 85, 128, 170, 213, 255); Scale08: array [0..7] of Byte = (0, 36, 73, 109, 146, 182, 219, 255); { For 6Rx6Gx6B, 7Rx8Gx4B palettes etc. } var TruncIndex04: array [Byte] of Byte; TruncIndex06: array [Byte] of Byte; TruncIndex07: array [Byte] of Byte; TruncIndex08: array [Byte] of Byte; { These functions initialises this module } procedure InitTruncTables; function NearestIndex(Value: Byte; const Bytes: array of Byte): Byte; var B, I: Byte; Diff, DiffMin: Word; begin Result := 0; B := Bytes[0]; DiffMin := Abs(Value - B); for I := 1 to High(Bytes) do begin B := Bytes[I]; Diff := Abs(Value - B); if Diff < DiffMin then begin DiffMin := Diff; Result := I; end; end; end; var I: Integer; begin { For 7 Red X 8 Green X 4 Blue palettes etc. } for I := 0 to 255 do begin TruncIndex04[I] := NearestIndex(Byte(I), Scale04); TruncIndex06[I] := NearestIndex(Byte(I), Scale06); TruncIndex07[I] := NearestIndex(Byte(I), Scale07); TruncIndex08[I] := NearestIndex(Byte(I), Scale08); end; end; procedure Trunc(const Header: TBitmapInfoHeader; Src, Dest: Pointer; DstBitsPerPixel: Integer; TruncLineProc: TTruncLine); var SrcScanline, DstScanline: Longint; Y: Integer; begin SrcScanline := (Header.biWidth * 3 + 3) and not 3; DstScanline := ((Header.biWidth * DstBitsPerPixel + 31) div 32) * 4; for Y := 0 to Header.biHeight - 1 do TruncLineProc(HugeOffset(Src, Y * SrcScanline), HugeOffset(Dest, Y * DstScanline), Header.biWidth); end; { return 6Rx6Gx6B palette This function makes the palette for the 6 red X 6 green X 6 blue palette. 216 palette entrys used. Remaining 40 Left blank. } procedure TruncPal6R6G6B(var Colors: TRGBPalette); var I, R, G, B: Byte; begin FillChar(Colors, SizeOf(TRGBPalette), $80); I := 0; for R := 0 to 5 do for G := 0 to 5 do for B := 0 to 5 do begin Colors[I].rgbRed := Scale06[R]; Colors[I].rgbGreen := Scale06[G]; Colors[I].rgbBlue := Scale06[B]; Colors[I].rgbReserved := 0; Inc(I); end; end; { truncate to 6Rx6Gx6B one line } procedure TruncLine6R6G6B(Src, Dest: Pointer; CX: Integer); far; var X: Integer; R, G, B: Byte; begin for X := 0 to CX - 1 do begin B := TruncIndex06[Byte(Src^)]; Src := HugeOffset(Src, 1); G := TruncIndex06[Byte(Src^)]; Src := HugeOffset(Src, 1); R := TruncIndex06[Byte(Src^)]; Src := HugeOffset(Src, 1); PByte(Dest)^ := 6 * (6 * R + G) + B; Dest := HugeOffset(Dest, 1); end; end; { truncate to 6Rx6Gx6B } procedure Trunc6R6G6B(const Header: TBitmapInfoHeader; const Data24, Data8: Pointer); begin Trunc(Header, Data24, Data8, 8, TruncLine6R6G6B); end; { return 7Rx8Gx4B palette This function makes the palette for the 7 red X 8 green X 4 blue palette. 224 palette entrys used. Remaining 32 Left blank. Colours calculated to match those used by 8514/A PM driver. } procedure TruncPal7R8G4B(var Colors: TRGBPalette); var I, R, G, B: Byte; begin FillChar(Colors, SizeOf(TRGBPalette), $80); I := 0; for R := 0 to 6 do for G := 0 to 7 do for B := 0 to 3 do begin Colors[I].rgbRed := Scale07[R]; Colors[I].rgbGreen := Scale08[G]; Colors[I].rgbBlue := Scale04[B]; Colors[I].rgbReserved := 0; Inc(I); end; end; { truncate to 7Rx8Gx4B one line } procedure TruncLine7R8G4B(Src, Dest: Pointer; CX: Integer); far; var X: Integer; R, G, B: Byte; begin for X := 0 to CX - 1 do begin B := TruncIndex04[Byte(Src^)]; Src := HugeOffset(Src, 1); G := TruncIndex08[Byte(Src^)]; Src := HugeOffset(Src, 1); R := TruncIndex07[Byte(Src^)]; Src := HugeOffset(Src, 1); PByte(Dest)^ := 4 * (8 * R + G) + B; Dest := HugeOffset(Dest, 1); end; end; { truncate to 7Rx8Gx4B } procedure Trunc7R8G4B(const Header: TBitmapInfoHeader; const Data24, Data8: Pointer); begin Trunc(Header, Data24, Data8, 8, TruncLine7R8G4B); end; { Grayscale support } procedure GrayPal(var Colors: TRGBPalette); var I: Byte; begin FillChar(Colors, SizeOf(TRGBPalette), 0); for I := 0 to 255 do FillChar(Colors[I], 3, I); end; procedure Grayscale(const Header: TBitmapInfoHeader; Data24, Data8: Pointer); var SrcScanline, DstScanline: Longint; Y, X: Integer; Src, Dest: PByte; R, G, B: Byte; begin SrcScanline := (Header.biWidth * 3 + 3) and not 3; DstScanline := (Header.biWidth + 3) and not 3; for Y := 0 to Header.biHeight - 1 do begin Src := Data24; Dest := Data8; for X := 0 to Header.biWidth - 1 do begin B := Src^; Src := HugeOffset(Src, 1); G := Src^; Src := HugeOffset(Src, 1); R := Src^; Src := HugeOffset(Src, 1); Dest^ := Byte(Longint(Word(R) * 77 + Word(G) * 150 + Word(B) * 29) shr 8); Dest := HugeOffset(Dest, 1); end; Data24 := HugeOffset(Data24, SrcScanline); Data8 := HugeOffset(Data8, DstScanline); end; end; { Tripel conversion } procedure TripelPal(var Colors: TRGBPalette); var I: Byte; begin FillChar(Colors, SizeOf(TRGBPalette), 0); for I := 0 to $40 do begin Colors[I].rgbRed := I shl 2; Colors[I + $40].rgbGreen := I shl 2; Colors[I + $80].rgbBlue := I shl 2; end; end; procedure Tripel(const Header: TBitmapInfoHeader; Data24, Data8: Pointer); var SrcScanline, DstScanline: Longint; Y, X: Integer; Src, Dest: PByte; R, G, B: Byte; begin SrcScanline := (Header.biWidth * 3 + 3) and not 3; DstScanline := (Header.biWidth + 3) and not 3; for Y := 0 to Header.biHeight - 1 do begin Src := Data24; Dest := Data8; for X := 0 to Header.biWidth - 1 do begin B := Src^; Src := HugeOffset(Src, 1); G := Src^; Src := HugeOffset(Src, 1); R := Src^; Src := HugeOffset(Src, 1); case ((X + Y) mod 3) of 0: Dest^ := Byte(R shr 2); 1: Dest^ := Byte($40 + (G shr 2)); 2: Dest^ := Byte($80 + (B shr 2)); end; Dest := HugeOffset(Dest, 1); end; Data24 := HugeOffset(Data24, SrcScanline); Data8 := HugeOffset(Data8, DstScanline); end; end; { Histogram/Frequency-of-use method of color reduction } const MAX_N_COLS = 2049; MAX_N_HASH = 5191; function Hash(R, G, B: Byte): Word; begin Result := Word(Longint(Longint(R + G) * Longint(G + B) * Longint(B + R)) mod MAX_N_HASH); end; type PFreqRecord = ^TFreqRecord; TFreqRecord = record B: Byte; G: Byte; R: Byte; Frequency: Longint; Nearest: Byte; end; PHist = ^THist; THist = record ColCount: Longint; Rm: Byte; Gm: Byte; Bm: Byte; Freqs: array [0..MAX_N_COLS - 1] of TFreqRecord; HashTable: array [0..MAX_N_HASH - 1] of Word; end; function CreateHistogram(R, G, B: Byte): PHist; { create empty histogram } begin GetMem(Result, SizeOf(THist)); with Result^ do begin Rm := R; Gm := G; Bm := B; ColCount := 0; end; FillChar(Result^.HashTable, MAX_N_HASH * SizeOf(Word), 255); end; procedure ClearHistogram(var Hist: PHist; R, G, B: Byte); begin with Hist^ do begin Rm := R; Gm := G; Bm := B; ColCount := 0; end; FillChar(Hist^.HashTable, MAX_N_HASH * SizeOf(Word), 255); end; procedure DeleteHistogram(var Hist: PHist); begin FreeMem(Hist, SizeOf(THist)); Hist := nil; end; function AddToHistogram(var Hist: THist; const Header: TBitmapInfoHeader; Data24: Pointer): Boolean; { add bitmap data to histogram } var Step24: Integer; HashColor, Index: Word; Rm, Gm, Bm, R, G, B: Byte; X, Y, ColCount: Longint; begin Step24 := ((Header.biWidth * 3 + 3) and not 3) - Header.biWidth * 3; Rm := Hist.Rm; Gm := Hist.Gm; Bm := Hist.Bm; ColCount := Hist.ColCount; for Y := 0 to Header.biHeight - 1 do begin for X := 0 to Header.biWidth - 1 do begin B := Byte(Data24^) and Bm; Data24 := HugeOffset(Data24, 1); G := Byte(Data24^) and Gm; Data24 := HugeOffset(Data24, 1); R := Byte(Data24^) and Rm; Data24 := HugeOffset(Data24, 1); HashColor := Hash(R, G, B); repeat Index := Hist.HashTable[HashColor]; if (Index = $FFFF) or ((Hist.Freqs[Index].R = R) and (Hist.Freqs[Index].G = G) and (Hist.Freqs[Index].B = B)) then Break; Inc(HashColor); if HashColor = MAX_N_HASH then HashColor := 0; until False; { Note: loop will always be broken out of } { We don't allow HashTable to fill up above half full } if Index = $FFFF then begin { Not found in Hash table } if ColCount = MAX_N_COLS then begin Result := False; Exit; end; Hist.Freqs[ColCount].Frequency := 1; Hist.Freqs[ColCount].B := B; Hist.Freqs[ColCount].G := G; Hist.Freqs[ColCount].R := R; Hist.HashTable[HashColor] := ColCount; Inc(ColCount); end else begin { Found in Hash table, update index } Inc(Hist.Freqs[Index].Frequency); end; end; Data24 := HugeOffset(Data24, Step24); end; Hist.ColCount := ColCount; Result := True; end; procedure PalHistogram(var Hist: THist; var Colors: TRGBPalette; ColorsWanted: Integer); { work out a palette from Hist } var I, J: Longint; MinDist, Dist: Longint; MaxJ, MinJ: Longint; DeltaB, DeltaG, DeltaR: Longint; MaxFreq: Longint; begin I := 0; MaxJ := 0; MinJ := 0; { Now find the ColorsWanted most frequently used ones } while (I < ColorsWanted) and (I < Hist.ColCount) do begin MaxFreq := 0; for J := 0 to Hist.ColCount - 1 do if Hist.Freqs[J].Frequency > MaxFreq then begin MaxJ := J; MaxFreq := Hist.Freqs[J].Frequency; end; Hist.Freqs[MaxJ].Nearest := Byte(I); Hist.Freqs[MaxJ].Frequency := 0; { Prevent later use of Freqs[MaxJ] } Colors[I].rgbBlue := Hist.Freqs[MaxJ].B; Colors[I].rgbGreen := Hist.Freqs[MaxJ].G; Colors[I].rgbRed := Hist.Freqs[MaxJ].R; Colors[I].rgbReserved := 0; Inc(I); end; { Unused palette entries will be medium grey } while I <= 255 do begin Colors[I].rgbRed := $80; Colors[I].rgbGreen := $80; Colors[I].rgbBlue := $80; Colors[I].rgbReserved := 0; Inc(I); end; { For the rest, find the closest one in the first ColorsWanted } for I := 0 to Hist.ColCount - 1 do begin if Hist.Freqs[I].Frequency <> 0 then begin MinDist := 3 * 256 * 256; for J := 0 to ColorsWanted - 1 do begin DeltaB := Hist.Freqs[I].B - Colors[J].rgbBlue; DeltaG := Hist.Freqs[I].G - Colors[J].rgbGreen; DeltaR := Hist.Freqs[I].R - Colors[J].rgbRed; Dist := Longint(DeltaR * DeltaR) + Longint(DeltaG * DeltaG) + Longint(DeltaB * DeltaB); if Dist < MinDist then begin MinDist := Dist; MinJ := J; end; end; Hist.Freqs[I].Nearest := Byte(MinJ); end; end; end; procedure MapHistogram(var Hist: THist; const Header: TBitmapInfoHeader; Data24, Data8: Pointer); { map bitmap data to Hist palette } var Step24: Integer; Step8: Integer; HashColor, Index: Longint; Rm, Gm, Bm, R, G, B: Byte; X, Y: Longint; begin Step24 := ((Header.biWidth * 3 + 3) and not 3) - Header.biWidth * 3; Step8 := ((Header.biWidth + 3) and not 3) - Header.biWidth; Rm := Hist.Rm; Gm := Hist.Gm; Bm := Hist.Bm; for Y := 0 to Header.biHeight - 1 do begin for X := 0 to Header.biWidth - 1 do begin B := Byte(Data24^) and Bm; Data24 := HugeOffset(Data24, 1); G := Byte(Data24^) and Gm; Data24 := HugeOffset(Data24, 1); R := Byte(Data24^) and Rm; Data24 := HugeOffset(Data24, 1); HashColor := Hash(R, G, B); repeat Index := Hist.HashTable[HashColor]; if (Hist.Freqs[Index].R = R) and (Hist.Freqs[Index].G = G) and (Hist.Freqs[Index].B = B) then Break; Inc(HashColor); if HashColor = MAX_N_HASH then HashColor := 0; until False; PByte(Data8)^ := Hist.Freqs[Index].Nearest; Data8 := HugeOffset(Data8, 1); end; Data24 := HugeOffset(Data24, Step24); Data8 := HugeOffset(Data8, Step8); end; end; procedure Histogram(const Header: TBitmapInfoHeader; var Colors: TRGBPalette; Data24, Data8: Pointer; ColorsWanted: Integer; Rm, Gm, Bm: Byte); { map single bitmap to frequency optimised palette } var Hist: PHist; begin Hist := CreateHistogram(Rm, Gm, Bm); try repeat if AddToHistogram(Hist^, Header, Data24) then Break else begin if Gm > Rm then Gm := Gm shl 1 else if Rm > Bm then Rm := Rm shl 1 else Bm := Bm shl 1; ClearHistogram(Hist, Rm, Gm, Bm); end; until False; { Above loop will always be exited as if masks get rough } { enough, ultimately number of unique colours < MAX_N_COLS } PalHistogram(Hist^, Colors, ColorsWanted); MapHistogram(Hist^, Header, Data24, Data8); finally DeleteHistogram(Hist); end; end; { expand to 24 bits-per-pixel } (* procedure ExpandTo24Bit(const Header: TBitmapInfoHeader; Colors: TRGBPalette; Data, NewData: Pointer); var Scanline, NewScanline: Longint; Y, X: Integer; Src, Dest: Pointer; C: Byte; begin if Header.biBitCount = 24 then begin Exit; end; Scanline := ((Header.biWidth * Header.biBitCount + 31) div 32) * 4; NewScanline := ((Header.biWidth * 3 + 3) and not 3); for Y := 0 to Header.biHeight - 1 do begin Src := HugeOffset(Data, Y * Scanline); Dest := HugeOffset(NewData, Y * NewScanline); case Header.biBitCount of 1: begin C := 0; for X := 0 to Header.biWidth - 1 do begin if (X and 7) = 0 then begin C := Byte(Src^); Src := HugeOffset(Src, 1); end else C := C shl 1; PByte(Dest)^ := Colors[C shr 7].rgbBlue; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C shr 7].rgbGreen; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C shr 7].rgbRed; Dest := HugeOffset(Dest, 1); end; end; 4: begin X := 0; while X < Header.biWidth - 1 do begin C := Byte(Src^); Src := HugeOffset(Src, 1); PByte(Dest)^ := Colors[C shr 4].rgbBlue; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C shr 4].rgbGreen; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C shr 4].rgbRed; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C and 15].rgbBlue; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C and 15].rgbGreen; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C and 15].rgbRed; Dest := HugeOffset(Dest, 1); Inc(X, 2); end; if X < Header.biWidth then begin C := Byte(Src^); PByte(Dest)^ := Colors[C shr 4].rgbBlue; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C shr 4].rgbGreen; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C shr 4].rgbRed; {Dest := HugeOffset(Dest, 1);} end; end; 8: begin for X := 0 to Header.biWidth - 1 do begin C := Byte(Src^); Src := HugeOffset(Src, 1); PByte(Dest)^ := Colors[C].rgbBlue; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C].rgbGreen; Dest := HugeOffset(Dest, 1); PByte(Dest)^ := Colors[C].rgbRed; Dest := HugeOffset(Dest, 1); end; end; end; end; end; *) { DIB utility routines } function GetPaletteBitmapFormat(Bitmap: TBitmap): TPixelFormat; var PalSize: Integer; begin Result := pfDevice; if Bitmap.Palette <> 0 then begin GetObject(Bitmap.Palette, SizeOf(Integer), @PalSize); if PalSize > 0 then begin if PalSize <= 2 then Result := pf1bit else if PalSize <= 16 then Result := pf4bit else if PalSize <= 256 then Result := pf8bit; end; end; end; function GetBitmapPixelFormat(Bitmap: TBitmap): TPixelFormat; {$IFDEF COMPILER3_UP} begin Result := Bitmap.PixelFormat; {$ELSE} var {$IFDEF WIN32} BM: Windows.TBitmap; {$ELSE} BM: WinTypes.TBitmap; {$ENDIF} begin Result := pfDevice; if Bitmap.Handle <> 0 then begin GetObject(Bitmap.Handle, SizeOf(BM), @BM); case BM.bmBitsPixel * BM.bmPlanes of 1: Result := pf1bit; 4: Result := pf4bit; 8: Result := pf8bit; 24: Result := pf24bit; end; end; {$ENDIF} end; function BytesPerScanline(PixelsPerScanline, BitsPerPixel, Alignment: Longint): Longint; begin Dec(Alignment); Result := ((PixelsPerScanline * BitsPerPixel) + Alignment) and not Alignment; Result := Result div 8; end; procedure InitializeBitmapInfoHeader(Bitmap: HBITMAP; var BI: TBitmapInfoHeader; PixelFormat: TPixelFormat); {$IFDEF WIN32} var DS: TDIBSection; Bytes: Integer; begin DS.dsbmih.biSize := 0; Bytes := GetObject(Bitmap, SizeOf(DS), @DS); if Bytes = 0 then InvalidBitmap else if (Bytes >= (SizeOf(DS.dsbm) + SizeOf(DS.dsbmih))) and (DS.dsbmih.biSize >= DWORD(SizeOf(DS.dsbmih))) then BI := DS.dsbmih else begin FillChar(BI, sizeof(BI), 0); with BI, DS.dsbm do begin biSize := SizeOf(BI); biWidth := bmWidth; biHeight := bmHeight; end; end; case PixelFormat of pf1bit: BI.biBitCount := 1; pf4bit: BI.biBitCount := 4; pf8bit: BI.biBitCount := 8; pf24bit: BI.biBitCount := 24; else BI.biBitCount := DS.dsbm.bmBitsPixel * DS.dsbm.bmPlanes; end; BI.biPlanes := 1; if BI.biSizeImage = 0 then BI.biSizeImage := BytesPerScanLine(BI.biWidth, BI.biBitCount, 32) * Abs(BI.biHeight); end; {$ELSE WIN32} var BM: WinTypes.TBitmap; begin GetObject(Bitmap, SizeOf(BM), @BM); with BI do begin biSize := SizeOf(BI); biWidth := BM.bmWidth; biHeight := BM.bmHeight; case PixelFormat of pf1bit: biBitCount := 1; pf4bit: biBitCount := 4; pf8bit: biBitCount := 8; pf24bit: biBitCount := 24; else biBitCount := BM.bmBitsPixel * BM.bmPlanes; end; biPlanes := 1; biXPelsPerMeter := 0; biYPelsPerMeter := 0; biClrUsed := 0; biClrImportant := 0; biCompression := BI_RGB; if biBitCount in [9..32] then biBitCount := 24; biSizeImage := (((biWidth * biBitCount + 31) div 32) * 4) * biHeight; end; end; {$ENDIF WIN32} procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: Integer; var ImageSize: Longint; BitCount: TPixelFormat); var BI: TBitmapInfoHeader; begin InitializeBitmapInfoHeader(Bitmap, BI, BitCount); if BI.biBitCount > 8 then begin InfoHeaderSize := SizeOf(TBitmapInfoHeader); {$IFDEF WIN32} if (BI.biCompression and BI_BITFIELDS) <> 0 then Inc(InfoHeaderSize, 12); {$ENDIF} end else InfoHeaderSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) * (1 shl BI.biBitCount); ImageSize := BI.biSizeImage; end; function InternalGetDIB(Bitmap: HBITMAP; Palette: HPALETTE; var BitmapInfo; var Bits; PixelFormat: TPixelFormat): Boolean; var OldPal: HPALETTE; DC: HDC; begin InitializeBitmapInfoHeader(Bitmap, TBitmapInfoHeader(BitmapInfo), PixelFormat); {$IFDEF WIN32} with TBitmapInfoHeader(BitmapInfo) do biHeight := Abs(biHeight); {$ENDIF} OldPal := 0; DC := CreateCompatibleDC(0); try if Palette <> 0 then begin OldPal := SelectPalette(DC, Palette, False); RealizePalette(DC); end; Result := GetDIBits(DC, Bitmap, 0, TBitmapInfoHeader(BitmapInfo).biHeight, @Bits, TBitmapInfo(BitmapInfo), DIB_RGB_COLORS) <> 0; finally if OldPal <> 0 then SelectPalette(DC, OldPal, False); DeleteDC(DC); end; end; function DIBFromBit(Src: HBITMAP; Pal: HPALETTE; PixelFormat: TPixelFormat; var Length: Longint): Pointer; var HeaderSize: Integer; ImageSize: Longint; FileHeader: PBitmapFileHeader; BI: PBitmapInfoHeader; Bits: Pointer; begin if Src = 0 then InvalidBitmap; InternalGetDIBSizes(Src, HeaderSize, ImageSize, PixelFormat); Length := SizeOf(TBitmapFileHeader) + HeaderSize + ImageSize; Result := AllocMemo(Length); try FillChar(Result^, Length, 0); FileHeader := Result; with FileHeader^ do begin bfType := $4D42; bfSize := Length; bfOffBits := SizeOf(FileHeader^) + HeaderSize; end; BI := PBitmapInfoHeader(Longint(FileHeader) + SizeOf(FileHeader^)); Bits := Pointer(Longint(BI) + HeaderSize); InternalGetDIB(Src, Pal, BI^, Bits^, PixelFormat); except FreeMemo(Result); raise; end; end; { Change bits per pixel in a General Bitmap } function BitmapToMemoryStream(Bitmap: TBitmap; PixelFormat: TPixelFormat; Method: TMappingMethod): TMemoryStream; var FileHeader: PBitmapFileHeader; BI, NewBI: PBitmapInfoHeader; Bits: Pointer; NewPalette: PRGBPalette; NewHeaderSize: Integer; ImageSize, Length, Len: Longint; P, InitData: Pointer; ColorCount: Integer; begin if Bitmap.Handle = 0 then InvalidBitmap; if (GetBitmapPixelFormat(Bitmap) = PixelFormat) and (Method <> mmGrayscale) then begin Result := TMemoryStream.Create; try Bitmap.SaveToStream(Result); Result.Position := 0; except Result.Free; raise; end; Exit; end; if not (PixelFormat in [pf1bit, pf4bit, pf8bit, pf24bit]) then NotImplemented else if PixelFormat in [pf1bit, pf4Bit] then begin P := DIBFromBit(Bitmap.Handle, Bitmap.Palette, PixelFormat, Length); try Result := TMemoryStream.Create; try Result.Write(P^, Length); Result.Position := 0; except Result.Free; raise; end; finally FreeMemo(P); end; Exit; end; { pf8bit - expand to 24bit first } InitData := DIBFromBit(Bitmap.Handle, Bitmap.Palette, pf24bit, Len); try BI := PBitmapInfoHeader(Longint(InitData) + SizeOf(TBitmapFileHeader)); if BI^.biBitCount <> 24 then NotImplemented; {!!!} Bits := Pointer(Longint(BI) + SizeOf(TBitmapInfoHeader)); InternalGetDIBSizes(Bitmap.Handle, NewHeaderSize, ImageSize, PixelFormat); Length := SizeOf(TBitmapFileHeader) + NewHeaderSize; P := AllocMemo(Length); try FillChar(P^, Length, #0); NewBI := PBitmapInfoHeader(Longint(P) + SizeOf(TBitmapFileHeader)); NewPalette := PRGBPalette(Longint(NewBI) + SizeOf(TBitmapInfoHeader)); FileHeader := PBitmapFileHeader(P); InitializeBitmapInfoHeader(Bitmap.Handle, NewBI^, PixelFormat); case Method of mmQuantize: begin ColorCount := 256; Quantize(BI^, Bits, Bits, ColorCount, NewPalette^); NewBI^.biClrImportant := ColorCount; end; mmTrunc784: begin TruncPal7R8G4B(NewPalette^); Trunc7R8G4B(BI^, Bits, Bits); NewBI^.biClrImportant := 224; end; mmTrunc666: begin TruncPal6R6G6B(NewPalette^); Trunc6R6G6B(BI^, Bits, Bits); NewBI^.biClrImportant := 216; end; mmTripel: begin TripelPal(NewPalette^); Tripel(BI^, Bits, Bits); end; mmHistogram: begin Histogram(BI^, NewPalette^, Bits, Bits, PixelFormatToColors(PixelFormat), 255, 255, 255); end; mmGrayscale: begin GrayPal(NewPalette^); GrayScale(BI^, Bits, Bits); end; end; { ************* BUGFIX ************ reduce ImageSize to adjust to pf8Bit } if PixelFormat = pf8bit then ImageSize := ImageSize div 3; { *********** END BUGFIX ********** } with FileHeader^ do begin bfType := $4D42; bfSize := Length; bfOffBits := SizeOf(FileHeader^) + NewHeaderSize; end; Result := TMemoryStream.Create; try Result.Write(P^, Length); Result.Write(Bits^, ImageSize); Result.Position := 0; except Result.Free; raise; end; finally FreeMemo(P); end; finally FreeMemo(InitData); end; end; function BitmapToMemory(Bitmap: TBitmap; Colors: Integer): TStream; var PixelFormat: TPixelFormat; begin if Colors <= 2 then PixelFormat := pf1bit else if Colors <= 16 then PixelFormat := pf4bit else if Colors <= 256 then PixelFormat := pf8bit else PixelFormat := pf24bit; Result := BitmapToMemoryStream(Bitmap, PixelFormat, DefaultMappingMethod); end; procedure SaveBitmapToFile(const Filename: string; Bitmap: TBitmap; Colors: Integer); var Memory: TStream; begin if Bitmap.Monochrome then Colors := 2; Memory := BitmapToMemory(Bitmap, Colors); try TMemoryStream(Memory).SaveToFile(Filename); finally Memory.Free; end; end; procedure SetBitmapPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat; Method: TMappingMethod); var M: TMemoryStream; begin if (Bitmap.Handle = 0) or (GetBitmapPixelFormat(Bitmap) = PixelFormat) then Exit; M := BitmapToMemoryStream(Bitmap, PixelFormat, Method); try Bitmap.LoadFromStream(M); finally M.Free; end; end; procedure GrayscaleBitmap(Bitmap: TBitmap); begin SetBitmapPixelFormat(Bitmap, pf8bit, mmGrayscale); end; function ZoomImage(ImageW, ImageH, MaxW, MaxH: Integer; Stretch: Boolean): TPoint; var Zoom: Double; begin Result := Point(0, 0); if (MaxW <= 0) or (MaxH <= 0) or (ImageW <= 0) or (ImageH <= 0) then Exit; with Result do if Stretch then begin Zoom := MaxFloat([ImageW / MaxW, ImageH / MaxH]); if Zoom > 0 then begin X := Round(ImageW * 0.98 / Zoom); Y := Round(ImageH * 0.98 / Zoom); end else begin X := ImageW; Y := ImageH; end; end else begin X := MaxW; Y := MaxH; end; end; procedure TileImage(Canvas: TCanvas; Rect: TRect; Image: TGraphic); var X, Y: Integer; SaveIndex: Integer; begin if (Image.Width = 0) or (Image.Height = 0) then Exit; SaveIndex := SaveDC(Canvas.Handle); try with Rect do IntersectClipRect(Canvas.Handle, Left, Top, Right, Bottom); for X := 0 to (WidthOf(Rect) div Image.Width) do for Y := 0 to (HeightOf(Rect) div Image.Height) do Canvas.Draw(Rect.Left + X * Image.Width, Rect.Top + Y * Image.Height, Image); finally RestoreDC(Canvas.Handle, SaveIndex); end; end; //=== TJvGradient ============================================================ constructor TJvGradient.Create; begin inherited Create; FStartColor := clSilver; FEndColor := clGray; FStepCount := 64; FDirection := fdTopToBottom; end; procedure TJvGradient.Assign(Source: TPersistent); begin if Source is TJvGradient then begin with TJvGradient(Source) do begin Self.FStartColor := StartColor; Self.FEndColor := EndColor; Self.FStepCount := StepCount; Self.FDirection := Direction; Self.FVisible := Visible; end; Changed; end else inherited Assign(Source); end; procedure TJvGradient.Changed; begin if Assigned(FOnChange) then FOnChange(Self); end; procedure TJvGradient.Draw(Canvas: TCanvas; Rect: TRect); begin GradientFillRect(Canvas, Rect, FStartColor, FEndColor, FDirection, FStepCount); end; procedure TJvGradient.SetStartColor(Value: TColor); begin if Value <> FStartColor then begin FStartColor := Value; Changed; end; end; procedure TJvGradient.SetEndColor(Value: TColor); begin if Value <> FEndColor then begin FEndColor := Value; Changed; end; end; procedure TJvGradient.SetDirection(Value: TFillDirection); begin if Value <> FDirection then begin FDirection := Value; Changed; end; end; procedure TJvGradient.SetStepCount(Value: Byte); begin if Value <> FStepCount then begin FStepCount := Value; Changed; end; end; procedure TJvGradient.SetVisible(Value: Boolean); begin if FVisible <> Value then begin FVisible := Value; Changed; end; end; initialization InitTruncTables; end.