home *** CD-ROM | disk | FTP | other *** search
/ DTP en Graphics 1 / dtpgraf1.zip / dtpgraf1 / GRAPHICS / GRAPHICS.H_P / PCX2GIF / PCX2GIFU.PAS < prev   
Pascal/Delphi Source File  |  1993-01-20  |  20KB  |  568 lines

  1. UNIT PCX2GIFU;
  2.   {Unit contains the function PcxToGif which compresses the X and Y
  3.   dimensions of the PCX file by the amount of the constant Lines2Compress
  4.   and stores in the GIF file.   Specifically designed for reducing by 3 fold
  5.   the large 2 color PCX files produced by the Logitech hand scanner to
  6.   smaller managable GIF files with 10 colors (3 squared plus black)
  7.   in gray scale.  Uses no graphics functions and no graphics screen display.
  8.  
  9.   Written by Rob Crockett [76167,1561] April 25, 1992.  The include file
  10.   CMPRSS.INC written by Bob Berry [76555,167] 1988.  Much of the PCX
  11.   routines are from the PCX.PAS unit written by
  12.   J. L. Allison  [71565,303] Dec 30, 1989.}
  13.  
  14.  
  15.  
  16. INTERFACE
  17.    Uses {Graph,} Dos;
  18.  
  19.    Function PcxToGif(PcxFileName,GifFileName: String; Compression: Integer):Integer;
  20.  
  21. IMPLEMENTATION
  22.  
  23. {I+}                {no I/O checking}
  24. {R-}                {no variable range checking}
  25.   Const
  26.     top_two_bits      =$C0;
  27.     bottom_six_bits   =$3F;
  28.  
  29.     NumberOfPlanes    =4;
  30.     NumberBytesPerPlane=250;              {x<=2000}
  31.    { Lines2Compress   =3;}            {1 (no compression) to 6 times..}
  32.                                     {verticle and horizontal compression}
  33.  
  34.   Type
  35.     rgb=array [1..3] of byte;
  36.     palette=array [0..15] of rgb;
  37.  
  38.     pcx_header=record
  39.       manufacturer:      byte;  {10 => zsoft  .pcx}
  40.       version:           byte;  { 0 => 2.5
  41.                                   2 => 2.8 w/palette information
  42.                                   3 => 2.8 w/o palette info
  43.                                   5 => 3.0                       }
  44.  
  45.       encoding:          byte;  { 1 => .pcx run length encoding}
  46.       bits_per_pixel:    byte;  { bits_per_pixel per plane }
  47.       x1,y1,x2,y2:       word;  { upper left, lower right corners }
  48.       hres:              word;  { horizontal resolution }
  49.       vres:              word;  { vertical resolution }
  50.       colormap:          array[0..15,0..2] of byte;  {rgb}      {palette;}
  51.       reserved:          byte;
  52.       nplanes:           byte;  {number of color planes}
  53.       bytes_per_line:    word;  {bytes_per_scan_line per plane
  54.                                  ALWAYS EVEN FOR .PCX FILES }
  55.       palette_info:      word;  { 1 => color/bw
  56.                                   2 => grayscale }
  57.       filler:            array [1..58] of byte; {pad to 128 bytes total}
  58.     end;
  59.  
  60.     FourPlaneLine     =Array [0..(NumberOfPlanes-1),0..(NumberBytesPerPlane-1)] of Byte;
  61.     FourLineLine      =Array [0..5,0..((NumberBytesPerPlane*8)-1)] of Byte;
  62.     OnePlaneLine      =Array [0..((NumberBytesPerPlane*8)-1)] of Byte;
  63.  
  64.     FourPlaneLinePointer=^FourPlaneLine;
  65.     FourLineLinePointer=^FourLineLine;
  66.     OnePlaneLinePointer=^OnePlaneLine;
  67.  
  68.   Var
  69.     Lines2Compress: Integer;
  70.     pcx_file          :file;
  71.     header            :pcx_header;
  72.     BufferLine: FourPlaneLinePointer;    {contains 4 bitplanes as bits}
  73.     ScreenLines: FourLineLinePointer;    {contains color numbers as bytes}
  74.     CompressedLine: OnePlaneLinePointer; {compressed color numbers as bytes}
  75.  
  76.   Type
  77.     ByteFile = File of Byte;
  78.  
  79.   Var
  80.     Signature    : Array[0..5] of Byte;            {GIF signature}
  81.     SDescriptor  : Array[1..7] of byte;            {screen descriptor}
  82.     ColorMap     : Array[0..2,0..255] of byte;     {RGB color map}
  83.     IDescriptor  : Array[1..10] of byte;           {image descriptor}
  84.     GifFile      : ByteFile;                       {output file}
  85.     Debugger     : Boolean;
  86.     XX,YY,GetMaxXX,GetMaxYY,GetMaxCColor: Integer;
  87.     GifTerminator: Byte;                           {';' GIF terminator}
  88.  
  89. {$I CMPRSS.INC}     {Include Bob Berry's LZW GIF compression routines}
  90.  
  91.   Procedure SetMaxConstants;
  92.     Begin
  93.       GetMaxXX:=((Header.bytes_per_line*8) div Lines2Compress)-1;
  94.       GetMaxYY:=((Header.Y2-Header.Y1+1) div Lines2Compress)-1;
  95.       If Lines2Compress<4
  96.         Then GetMaxCColor:=15
  97.         Else GetMaxCColor:=255;
  98.     End;
  99.  
  100.   Procedure OpenGifFile(FileName:String;Var GifFile: ByteFile);
  101.      Var
  102.        ErrorCode: Integer;
  103.      Begin
  104.        Assign(GifFile,FileName);              {open output file}
  105.        {$I-}ReWrite(GifFile);{$I+}
  106.        ErrorCode:=IOResult;
  107.        If ErrorCode<>0 then
  108.          Begin
  109.            Writeln('Error #',ErrorCode,' while opening ',FileName);
  110.            Halt;
  111.          End;
  112.      End;
  113.  
  114.   Procedure OpenPcxFile(FileName:String;Var PcxFile: File);
  115.      Var
  116.        ErrorCode: Integer;
  117.      Begin
  118.        Assign(PcxFile,filename);       {open input file}
  119.        {$I-}Reset(PcxFile,1);{$I+}
  120.        ErrorCode:=IOResult;
  121.        If ErrorCode<>0 then
  122.          Begin
  123.            Writeln('Error #',ErrorCode,' while accessing ',FileName);
  124.            Halt;
  125.          End;
  126.      End;
  127.  
  128.   Procedure ClosePcxFile(Var FileLabel: File);
  129.      Begin
  130.        Close(FileLabel);
  131.      End;
  132.  
  133.   Procedure CloseGifFile(Var FileLabel: ByteFile);
  134.      Begin
  135.        Close(FileLabel);
  136.      End;
  137.  
  138.   Procedure GetAllRGBPalette(ColorNum:Integer;Var RedNum,GreenNum,BlueNum:Byte);
  139.      {specialized palette for 10 color black and white where lines2compress=3}
  140.     Var
  141.       Color: Byte;
  142.     Begin
  143.       If (Header.nplanes>1)
  144.         Then
  145.           Begin
  146.             RedNum:=Header.ColorMap[ColorNum,0];
  147.             BlueNum:=Header.ColorMap[ColorNum,1];
  148.             GreenNum:=Header.ColorMap[ColorNum,2];
  149.           End
  150.         Else
  151.           Begin
  152.             If ColorNum<=SQR(Lines2Compress)
  153.               Then Color:=(ColorNum*255) div SQR(Lines2Compress)
  154.               Else Color:=1;
  155.             RedNum:=Color;GreenNum:=Color;BlueNum:=Color;
  156.           End;
  157.     End;
  158.  
  159.   Procedure SaveDescriptor(Var GifFile:ByteFile);
  160.      Var
  161.        I,J                 :Integer;
  162.        Pixel,MaxColor      :Byte;
  163.      Begin
  164.        For I:=0 to 5 do Write(GifFile,Signature[I]);
  165.        For I:=1 to 7 do Write(Giffile,SDescriptor[I]);
  166.        For J:=0 to GetMaxCColor {MaxColor} do
  167.          For I:=0 to 2 do Write(GifFile,ColorMap[I,J]);
  168.        For I:=1 to 10 do Write(GifFile,IDescriptor[I]);
  169.      End;
  170.  
  171.   Procedure SetGifDescriptor;
  172.     {sets the gif signature 'Signature[0..5]', screen
  173.      descriptor array 'SDescriptor[1..7]', and global color
  174.      map as follows:
  175.        Signature = GIF87a as six bytes
  176.        Screen Descriptor as seven bytes:
  177.  
  178.              bits
  179.          7 6 5 4 3 2 1 0  Byte #
  180.         +---------------+
  181.         |               |  1
  182.         +-Screen Width -+      Raster width in pixels (LSB first)
  183.         |               |  2
  184.         +---------------+
  185.         |               |  3
  186.         +-Screen Height-+      Raster height in pixels (LSB first)
  187.         |               |  4
  188.         +-+-----+-+-----+      M = 1, Global color map follows Descriptor
  189.         |M|  cr |0|pixel|  5   cr+1 = # bits of color resolution
  190.         +-+-----+-+-----+      pixel+1 = # bits/pixel in image (bit 3 of word 5 reserved)
  191.         |   background  |  6   background=Color index of screen background
  192.         +---------------+          (color is defined from the Global color
  193.         |0 0 0 0 0 0 0 0|  7        map or default map if none specified)
  194.         +---------------+
  195.  
  196.        Global Color Map has 3*GetMaxColor bytes.
  197.  
  198.     }
  199.  
  200.     Const
  201.       X = 1;
  202.       Y = 1;
  203.       X1 = 40;
  204.       Y1 = 1;
  205.       GIF87a: Array[0..5] of Byte = (71,73,70,56,55,97);
  206.  
  207.  
  208.     Var
  209.       I,J                       :Integer;
  210.       CR,Pixel                  :Byte;
  211.       {Regs: Registers;}
  212.     Begin
  213.       {*** SCREEN DESCRIPTOR ******************************}
  214.  
  215.       {Signature}
  216.       For I:=0 to 5 do
  217.         Signature[I]:=GIF87a[I];
  218.  
  219.       {Screen Width}
  220.       SDescriptor[1]:=(GetMaxXX+1) Mod 256;
  221.       SDescriptor[2]:=(GetMaxXX+1) Div 256;
  222.  
  223.       {Screen Height}
  224.       SDescriptor[3]:=(GetMaxYY+1) Mod 256;
  225.       SDescriptor[4]:=(GetMaxYY+1) Div 256;
  226.  
  227.  
  228.       SDescriptor[5]:=0;
  229.  
  230.       {M=1}
  231.       SDescriptor[5]:=SDescriptor[5] OR 128; {1000000}
  232.  
  233.       {CR+1=bits color resolution}
  234.       CR:=1;
  235.       Case GetMaxCColor of    {CR+1=color resolution=bits per RGB color componant}
  236.          1: CR:=0;
  237.          3: CR:=0;
  238.         15: CR:=1;
  239.        255: CR:=7;
  240.       End;
  241.       SDescriptor[5]:=SDescriptor[5] OR (CR shl 4);
  242.  
  243.       {Pixel+1=bits per pixel in image}
  244.       Pixel:=3;
  245.       Case GetMaxCColor of
  246.          1: Pixel:=0;
  247.          3: Pixel:=1;
  248.         15: Pixel:=3;
  249.        255: pixel:=7;
  250.       End;
  251.       SDescriptor[5]:=SDescriptor[5] OR Pixel;
  252.  
  253.       {Background color}
  254.       SDescriptor[6]:=0;         {set as black}
  255.  
  256.       {Reserved}
  257.       SDescriptor[7]:=0;
  258.  
  259.  
  260.       {****** Global Color Map *********************}
  261.  
  262.       For I:=0 to GetMaxCColor do
  263.          GetAllRGBPalette(I,ColorMap[0,I],ColorMap[1,I],ColorMap[2,I]);
  264.  
  265.       {*** IMAGE DESCRIPTOR *****************************}
  266.  
  267.       {ImageSepChar ',' }
  268.       IDescriptor[1]:=Ord(',');
  269.  
  270.       {Image Left}
  271.       IDescriptor[2]:=0 mod 256;
  272.       IDescriptor[3]:=0 div 256;
  273.  
  274.       {Image Top}
  275.       IDescriptor[4]:=0 mod 256;
  276.       IDescriptor[5]:=0 div 256;
  277.  
  278.       {Image Width}
  279.       IDescriptor[6]:=(GetMaxXX+1) mod 256;
  280.       IDescriptor[7]:=(GetMaxXX+1) div 256;
  281.  
  282.       {Image Height}
  283.       IDescriptor[8]:=(GetMaxYY+1) mod 256;
  284.       IDescriptor[9]:=(GetMaxYY+1) div 256;
  285.  
  286.       {ImageSpecByte}
  287.         IDescriptor[10]:=0;
  288.       {M=1 local color map follows, use 'pixel'}
  289.       {M=0 use global color map, ignore 'pixel'}
  290.         {IDescriptor[10]:=IDescriptor[10] OR 128;} {10000000}
  291.       {I=0 formatted in sequential order}
  292.       {I=1 formatted in interlaced order}
  293.         {IDescriptor[10]:=IDescriptor[10] OR 64;}  {01000000}
  294.       {Pixel+1=bits per pixel for this image}
  295.         Case GetMaxCColor of
  296.           1: Pixel:=0;
  297.           3: Pixel:=1;
  298.          15: Pixel:=3;
  299.         255: pixel:=7;
  300.         End;
  301.         IDescriptor[10]:=IDescriptor[10] OR Pixel;
  302.     End;
  303.  
  304. Procedure step_line_pos(var pos:integer;var plane:byte);
  305.   begin
  306.     inc(pos);
  307.     if pos>=header.bytes_per_line
  308.       then
  309.         begin
  310.           pos:=0;
  311.           inc(plane);
  312.           if plane>header.nplanes then exit;
  313.         end;
  314.   end;
  315.  
  316. Procedure CompressLine;
  317.   {Compresses Lines2Compress*Lines2Compress color numbers stored
  318.    in ScreenLines and puts the result in Compressed line.  If
  319.    Lines2Compress=3, then 9 color numbers are compressed to form one
  320.    pixel.  In a black and white image, this increases the number of colors
  321.    from 2 to 10 (black plus 3*3=9).}
  322.   Var
  323.     I,J,K,L,M,N,X:Integer;
  324.   Begin
  325.     For X:=0 to GetMaxXX do
  326.       CompressedLine^[X]:=0;
  327.     M:=Lines2Compress-1;
  328.     K:=0;
  329.     N:=SQR(Lines2Compress);
  330.     If (Header.Nplanes>1)          {color display--average the pixels}
  331.       Then
  332.         Begin
  333.           For X:=0 to GetMaxXX do
  334.             Begin
  335.               For I:=0 to M do                         {horizontal}
  336.                 Begin
  337.                   L:=K+I;
  338.                   For J:=0 to M do                     {verticle}
  339.                     CompressedLine^[X]:=CompressedLine^[X]
  340.                      +ScreenLines^[J,L];               {add pixels}
  341.                 End;
  342.               CompressedLine^[X]:=CompressedLine^[X] div N; {average pixels}
  343.               K:=K+Lines2Compress;
  344.             End;
  345.         End
  346.       Else                         {black and white--add the pixels}
  347.         Begin
  348.           For X:=0 to GetMaxXX do
  349.             Begin
  350.               For I:=0 to M do                         {horizontal}
  351.                 Begin
  352.                   L:=K+I;
  353.                   For J:=0 to M do                     {verticle}
  354.                     CompressedLine^[X]:=CompressedLine^[X]
  355.                      +ScreenLines^[J,L];               {add pixels}
  356.                 End;
  357.               K:=K+Lines2Compress;
  358.             End;
  359.         End;
  360.   End;
  361.  
  362.   Procedure RefreshCompressedLine;
  363.     Var
  364.       count,value,plane   :byte;
  365.       running_count,
  366.       X,Y,I,J,K,
  367.       PlaneNumber,
  368.       CmprssCntr          :Integer;
  369.     Begin
  370.       For CmprssCntr:=0 to Lines2Compress-1 do
  371.         Begin
  372.           plane:=1;
  373.           running_count:=0;                  {byte count in X direction}
  374.  
  375.     {****get bit plane bits from PCX file****}
  376.     {store up to 4 bit planes in BufferLine as bits}
  377.           repeat
  378.             blockread(pcx_file,count,1);
  379.             if (count and top_two_bits) = top_two_bits { top two are set }
  380.               then
  381.                 begin
  382.                   count:=count and bottom_six_bits;
  383.                   blockread(pcx_file,value,1);
  384.                   for I:=1 to count do
  385.                     begin
  386.                       BufferLine^[Plane-1,Running_Count]:=Value;
  387.                       step_line_pos(running_count,plane);
  388.                     end;
  389.                 end
  390.               else
  391.                 begin
  392.                   BufferLine^[Plane-1,Running_Count]:=Count;
  393.                   step_line_pos(running_count,plane);
  394.                 end;
  395.           until plane>header.nplanes;
  396.  
  397.    {****convert bit plane bits to color number bytes****}
  398.    {convert Bufferline bit plane bits to ScreenLines color number bytes}
  399.  
  400.           For I:=0 to ((header.bytes_per_line*8)-1) do
  401.             ScreenLines^[CmprssCntr,I]:=0;           {clear the buffer}
  402.           K:=0;                                      {repeat for each Y line}
  403.           For J:=0 to (header.bytes_per_line-1) do   {X direction byte count}
  404.             For I:=7 downto 0 do                     {high bit to low bit}
  405.               Begin
  406.                 For PlaneNumber:=Header.nplanes-1 downto 0 do
  407.                                                      {high plane to low plane}
  408.                   Begin                              {convert bit planes to byte}
  409.                     ScreenLines^[CmprssCntr,K]
  410.                       :=ScreenLines^[CmprssCntr,K] shl 1;  {advance prev bit}
  411.                     If Odd(BufferLine^[PlaneNumber,J] shr I)
  412.                        Then ScreenLines^[CmprssCntr,K]
  413.                               :=Succ(ScreenLines^[CmprssCntr,K]);
  414.                   End;
  415.                 K:=Succ(K);                          {X direction color number cnt}
  416.               End;
  417.         End;     {of for CmprssCntr:=0 to Lines2Compress-1}
  418.  
  419.       {******Compress ScreenLine color numbers to CompressedLine color}
  420.       {numbers.  Compress by factor of Lines2Compress}
  421.       CompressLine;
  422.     End;      {of refreshcompressedline}
  423.  
  424. Function GetByte:Integer;
  425.       {Called by the LZW compression routines, GetByte produces
  426.        a byte representing the color value of a pixel.
  427.        The byte is packaged as the low byte of a word (integer).
  428.        GetByte uses the global variables XX,YY to keep track of
  429.        its position in the array CompressedLine, the source of the bytes, and
  430.        refreshes the CompressedLine array when empty (when
  431.        XX=GetMaxXX).}
  432.  
  433.       Begin
  434.         If XX<GetMaxXX
  435.           Then
  436.             Begin
  437.               XX:=Succ(XX);
  438.               GetByte:=CompressedLine^[XX];
  439.               {PutPixel(XX,YY,CompressedLine^[XX]); }
  440.             End
  441.           Else
  442.             Begin
  443.               If YY<GetMaxYY           {-Lines2Compress+1)}
  444.                 Then
  445.                   Begin
  446.                     XX:=0;
  447.                     YY:=Succ(YY);
  448.                     RefreshCompressedLine;
  449.                     GetByte:=CompressedLine^[XX];
  450.                     {PutPixel(XX,YY,CompressedLine^[XX]);}
  451.                   End
  452.                 Else
  453.                   Begin
  454.                     GetByte:=(-1);
  455.                   End;
  456.             End;
  457.       End;
  458.  
  459.  Procedure PutByte(B:Integer);
  460.     {Called by the LZW compression routines, PutByte sends a byte
  461.      of data to the forming GIF file.  The first byte sent by the LZW
  462.      compression routings is the 'Minimum Code Size', next byte is
  463.      the block size, then the bytes forming the data block, the next
  464.      block size byte, next block data bytes, etc.  The byte is accepted
  465.      from the compression routines as the low byte of an integer.}
  466.  
  467.     Var
  468.       ByteNum: Byte;
  469.     Begin
  470.       ByteNum:=Lo(B);
  471.       Write(GifFile,ByteNum);
  472.     End;
  473.  
  474. Function GetMinCodeSize: Byte;
  475.     {Produces the minimum number of bits required to represent
  476.      the actual pixel value (the color number).  This would be the same
  477.      as the bits per pixel except that because of algorithmic constraints
  478.      of the LZW compression routines, black and white images with a
  479.      bits-per-pixel of 1 have a minimum code size of 2.  Thus the
  480.      mincodesize can be from 2 (black and white) to 8 (256 colors).}
  481.   Var
  482.     Code: Byte;
  483.   Begin
  484.     Case GetMaxCColor of
  485.        1: Code:=2;
  486.        3: Code:=2;
  487.       15: Code:=4;
  488.      255: Code:=8;
  489.     End;
  490.     GetMinCodeSize:=Code;
  491.   End;
  492.  
  493. (*
  494. Procedure PlotLine(Line: Word);
  495.   Var
  496.     XMax,Y,X: Integer;
  497.   Begin
  498.    XMax:=((header.bytes_per_line*8) div Lines2Compress);
  499.    For X:=0 to XMax do
  500.      PutPixel(X+Header.X1,Line+Header.Y1,CompressedLine^[X]);
  501.   End;
  502. *)
  503. Function GetSeconds:Real;
  504.   Var
  505.     Hour,Minute,Second,Sec100 :Word;
  506.   Begin
  507.     GetTime(Hour,Minute,Second,Sec100);
  508.     GetSeconds:=Hour*3600.0+Minute*60.0+Second+Sec100/100.0;
  509.   End;
  510.  
  511. {****MAIN PROGRAM**************************}
  512.  
  513.   Function PcxToGif(PcxFileName,GifFileName: String; Compression:Integer):Integer;
  514.     {Master function to create a GIF file 'FileName' from the graphics screen.
  515.      Returns ScreenToGif=0 if no errors, ScreenToGif<>0 if some error
  516.      encountered.}
  517.     Var
  518.       GifResult,I,GraphDriver,
  519.       Graphmode                     :Integer;
  520.       T0,T1                         :Real;
  521.       {global var  XX,YY : Integer}
  522.     Begin
  523.       Lines2Compress:=Compression;
  524.       OpenPcxFile(PcxFileName,pcx_file);    {open file for input}
  525.       OpenGifFile(GifFileName,GifFile);     {open the file for output}
  526.       Blockread(pcx_file,header,sizeof(header));   {read PCX header}
  527.       SetMaxConstants;                      {set getmaxx,getmaxy,getmaxcolor}
  528.  
  529.       Write('Input  file ',PcxFileName,' width: ',Header.Bytes_per_line*8:4);
  530.       Writeln(' height: ',Header.Y2-Header.Y1+1:4);
  531.       Write('Output file ',GifFileName,' width: ',GetMaxXX+1:4);
  532.       Writeln(' height: ',GetMaxYY+1:4);
  533.  
  534.    {   GraphDriver:=Detect;
  535.       InitGraph(GraphDriver,GraphMode,'');
  536.    }
  537.       New(BufferLine);                      {reserve space for arrays}
  538.       New(ScreenLines);
  539.       New(CompressedLine);
  540.       T0:=GetSeconds;                       {Time check T0}
  541.       RefreshCompressedLine;                {get first several PCX lines}
  542.       T1:=GetSeconds;                       {Time check T1}
  543.       Writeln('Estimated Time (min): ',(((T1-T0)*GetMaxYY)/43.0):4:1);
  544.       Writeln('Working...');
  545.       SetGifDescriptor;                     {set up GIF file info header}
  546.       SaveDescriptor(GifFile);              {send info header to GIF file}
  547.       XX:=-1;                               {set (X,Y) for screen location...}
  548.       YY:=0;                                {...used in GetByte function.}
  549.       T0:=GetSeconds;
  550.       GifResult:= CompressGif(GetMinCodeSize);  {send pcx data to GIF file}
  551.       T1:=GetSeconds;
  552.       WriteLn('Time used      (min): ',(T1-T0)/60.0:4:1);
  553.       GifTerminator:=$3B;                   {';'}
  554.       Write(GifFile,GifTerminator);         {send ';' to end GIF mode}
  555.       Dispose(ScreenLines);                 {free up memory}
  556.       Dispose(BufferLine);
  557.       Dispose(CompressedLine);
  558.       CloseGifFile(GifFile);                {close the file}
  559.       ClosePcxFile(pcx_file);
  560.  
  561.  
  562.    {   Readln;
  563.       CloseGraph;
  564.    }
  565.       PcxToGif:=GifResult;                  {pass along error codes}
  566.     End;
  567.  
  568. END.