{**********************************************}
{   TeeChart JPEG related functions            }
{   Copyright (c) 1996-2020 by Steema Software }
{**********************************************}
unit TeeJPEG;
{$I TeeDefs.inc}

{$IFDEF TEEOCX}
{$I TeeAXDefs.inc}
{$ENDIF}

interface

uses
  {$IFNDEF LINUX}
  Windows, Messages,
  {$ENDIF}
  SysUtils, Classes,

  Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls,
  {$IFDEF D9}
  Types,
  {$ENDIF}

  {$IFDEF LCL}
  FPReadJPEG,
  {$ELSE}
  JPEG,
  {$ENDIF}

  TeeProcs, TeeExport, TeCanvas;

type
  TTeeJPEGOptions = class(TForm)
    CBGray: TCheckBox;
    RGPerf: TRadioGroup;
    Label1: TLabel;
    EQuality: TEdit;
    UpDown1: TUpDown;
    Label2: TLabel;
    EDpi: TEdit;
    UDDpi: TUpDown;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  {$IFDEF LCL}
  TJPEGDefaults=packed record
      GrayScale            : Boolean;
      ProgressiveEncoding  : Boolean;
      CompressionQuality   : Integer;
      Performance          : TJPEGPerformance;
  end;

const
  JPEGDefaults:TJPEGDefaults=(
     GrayScale           : False;
     ProgressiveEncoding : False;
     CompressionQuality  : 95;
     Performance         : jpBestQuality;
                             );

type
  {$ENDIF}

  {$IFDEF TEENOJPEG}
  TJPEGPerformance=(jpBestQuality, jpSpeed);

  TJPEGDefaults=packed record
      GrayScale            : Boolean;
      ProgressiveEncoding  : Boolean;
      CompressionQuality   : Integer;
      Dpi                  : Integer;
      PixelFormat          : TPixelFormat;
      ProgressiveDisplay   : Boolean;
      Performance          : TJPEGPerformance;
      Scale                : Integer;
      Smoothing            : Boolean;
  end;

const
   JPEGDefaults:TJPEGDefaults=(
      GrayScale           : False;
      ProgressiveEncoding : False;
      CompressionQuality  : 95;
      Dpi                 : 0;
      PixelFormat         : TeePixelFormat;
      ProgressiveDisplay  : False;
      Performance         : jpBestQuality;
      Scale               : 1;
      Smoothing           : True;
                              );
type
  TJPEGImage=class(TBitmap)
  public
      GrayScale            : Boolean;
      ProgressiveEncoding  : Boolean;
      CompressionQuality   : Integer;
      Dpi                  : Integer;
      PixelFormat          : TPixelFormat;
      ProgressiveDisplay   : Boolean;
      Performance          : TJPEGPerformance;
      Scale                : Integer;
      Smoothing            : Boolean;
  end;
  {$ENDIF}

  TJPEGExportFormat=class(TTeeExportFormat)
  private
    Procedure CheckProperties;
    function GetQuality: Integer;
    procedure SetQuality(const Value: Integer);
  protected
    FProperties : TTeeJPEGOptions;
    Procedure DoCopyToClipboard; override;
  public
    Destructor Destroy; override;

    function Description:String; override;
    function FileExtension:String; override;
    class function FileFilter:String; override;
    function GraphicClass:TGraphicClass; override;
    Function Jpeg(const AOptions:TTeeJPEGOptions):TJPEGImage;
    Function Options(Check:Boolean=True):TForm; override;
    Procedure SaveToStream(Stream:TStream); override;
    Procedure SaveToStream(Stream:TStream; const AOptions:TForm); override;
    procedure SetDpiJPEG(const Stream: TStream; Rx,Ry: Word);

    property Quality:Integer read GetQuality write SetQuality default 95;
  end;

{ Returns a JPEG image from "APanel" Chart, DBChart or Draw3D }
Function TeeGetJPEGImageParams( const APanel:TCustomTeePanel;
                                Const Params:TJPEGDefaults;
                                Left,Top,
                                Width,Height:Integer):TJPEGImage;

{ saves a Panel (Chart, Tree, etc) into a JPEG file }
procedure TeeSaveToJPEGFile( const APanel:TCustomTeePanel;
                             Const FileName: WideString;
                             Gray: WordBool;
                             Performance: TJPEGPerformance;
                             Quality, AWidth, AHeight: Integer;
                             Dpi: Integer = 0);

{ same as above, with 100% quality }
procedure TeeSaveToJPEG( const APanel:TCustomTeePanel;
                         Const FileName: WideString;
                         AWidth, AHeight: Integer;
                         Dpi: Integer = 0);

implementation

{$IFNDEF LCL}
{$R *.DFM}
{$ELSE}
{$R *.lfm}
{$ENDIF}

uses
  Clipbrd, TeeConst;

function JPEGFromBitmap(const ABitmap:TBitmap; const Params:TJPEGDefaults):TJPEGImage;
begin
  { create the resulting JPEG image }
  result:=TJPEGImage.Create;

  { set the desired JPEG options... }
  with result do
  begin
    {$IFNDEF LCL}
    GrayScale            :=Params.GrayScale;
    PixelFormat          :=Params.PixelFormat;
    Scale                :=Params.Scale;
    Smoothing            :=Params.Smoothing;
    ProgressiveDisplay   :=Params.ProgressiveDisplay;
    ProgressiveEncoding  :=Params.ProgressiveEncoding;
    {$ENDIF}

    CompressionQuality   :=Params.CompressionQuality;
    Performance          :=Params.Performance;

    { Copy the temporary Bitmap onto the JPEG image... }
    Assign(ABitmap);
  end;
end;

Function TeeGetJPEGImageParams( const APanel:TCustomTeePanel;
                                Const Params:TJPEGDefaults;
                                Left,Top,
                                Width,Height:Integer):TJPEGImage;
var tmpBitmap : TBitmap;
begin
  { converts a TChart or TTree panel to JPEG }

  { create a temporary bitmap }
  tmpBitmap:=APanel.TeeCreateBitmap(APanel.Color,
                       TeeRect(Left,Top,Left+Width,Top+Height),
                       TeePixelFormat);
  try
    result:=JPEGFromBitmap(tmpBitmap,Params);
  finally
    tmpBitmap.Free;  { <-- free the temporary Bitmap }
  end;
end;

{ This function creates a JPEG Image, sets the desired JPEG
  parameters, draws a Chart (or TeeTree) on it, Saves the JPEG on disk and
  Loads the JPEG from disk to show it on this Form. }
procedure TeeSaveToJPEGFile( const APanel:TCustomTeePanel;
                             Const FileName: WideString;
                             Gray: WordBool;
                             Performance: TJPEGPerformance;
                             Quality, AWidth, AHeight: Integer;
                             Dpi: Integer = 0);
var tmp         : String;
    tmpWidth,
    tmpHeight   : Integer;
    FProperties : TTeeJPEGOptions;
begin
  { verify filename extension }
  tmp:=FileName;

  if ExtractFileExt(tmp)='' then
     tmp:=tmp+'.jpg'; // Do not localize

  FProperties:=TTeeJPEGOptions.Create(nil);
  FProperties.CBGray.Checked:=Gray;
  FProperties.UpDown1.Position:=Quality;
  FProperties.RGPerf.ItemIndex:=Integer(Performance);
  FProperties.UDDpi.Position:=Dpi;

  if AWidth<=0 then tmpWidth:=APanel.Width
               else tmpWidth:=AWidth;
  if AHeight<=0 then tmpHeight:=APanel.Height
                else tmpHeight:=AHeight;

  TJPEGExportFormat.SaveToFile(APanel, tmp, FProperties, tmpWidth, tmpHeight);
end;

procedure TeeSaveToJPEG( const APanel:TCustomTeePanel;
                         Const FileName: WideString;
                         AWidth, AHeight: Integer;
                         Dpi: Integer = 0);
begin
  TeeSaveToJPEGFile(APanel,FileName,False,jpBestQuality,100,AWidth,AHeight,Dpi);
end;

function TJPEGExportFormat.Description:String;
begin
  result:=TeeMsg_AsJPEG;
end;

function TJPEGExportFormat.FileExtension: String;
begin
  result:='jpg'; // Do not localize
end;

class function TJPEGExportFormat.FileFilter:String;
begin
  result:=TeeMsg_JPEGFilter;
end;

Function TJPEGExportFormat.Jpeg(const AOptions:TTeeJPEGOptions):TJPEGImage;
var Params : TJPEGDefaults;
    tmpBitmap : TBitmap;
begin
  { returns a JPEG image using the "Panel" (Chart) property }
  if not Assigned(Panel) then
     Raise Exception.Create(TeeMsg_ExportPanelNotSet);

  if Assigned(AOptions) then FProperties:=AOptions
                        else CheckProperties;

  { set JPEG options }
  Params:=JPEGDefaults;

  Params.GrayScale:=FProperties.CBGray.Checked;
  Params.CompressionQuality:=FProperties.UpDown1.Position;
  Params.Performance:=TJPEGPerformance(FProperties.RGPerf.ItemIndex);

  CheckSize;

  { obtain a JPEG image using parameters }
  tmpBitmap:=GetBitmap(TeePixelFormat);
  try
    result:=JPEGFromBitmap(tmpBitmap,Params);
  finally
    tmpBitmap.Free;
  end;
end;

Procedure TJPEGExportFormat.CheckProperties;
begin
  if not Assigned(FProperties) then
     FProperties:=TTeeJPEGOptions.Create(nil);
end;

Function TJPEGExportFormat.Options(Check:Boolean=True):TForm;
begin
  if Check then CheckProperties;
  result:=FProperties;
end;

procedure TJPEGExportFormat.DoCopyToClipboard;
var tmp : TJPEGImage;
begin
  tmp:=Jpeg(nil);
  try
    Clipboard.Assign(tmp);
  finally
    tmp.Free;
  end;
end;

procedure TJPEGExportFormat.SaveToStream(Stream:TStream);
begin
  SaveToStream(Stream, nil);
end;

procedure TTeeJPEGOptions.FormCreate(Sender: TObject);
begin
  Align:=alClient;
end;

function TJPEGExportFormat.GetQuality: Integer;
begin
  Options;
  result:=FProperties.UpDown1.Position;
end;

procedure TJPEGExportFormat.SaveToStream(Stream: TStream; const AOptions: TForm);
var dpi: Integer;
begin
  with Jpeg(TTeeJPEGOptions(AOptions)) do
  try
    SaveToStream(Stream);
    dpi:=FProperties.UDDpi.Position;
    SetDpiJPEG(Stream,dpi,dpi);
  finally
    Free;
  end;
end;

//http://www.clubdelphi.com/foros/archive/index.php/t-42148.html
procedure TJPEGExportFormat.SetDpiJPEG(const Stream: TStream; Rx,Ry: Word);
var
  AP0       : array[1..18] of Byte;
  Buffer    : array[0..$FFFF] of Char;
  Header    : array[1..4] of Byte;
  Size      : Integer;
  tmpStream : TMemoryStream;
begin
  tmpStream:=TMemoryStream.Create;

  try
    tmpStream.CopyFrom(Stream,0);
    Stream.Position:=0;

    tmpStream.Seek(0, {$IFDEF D8}soBeginning{$ELSE}soFromBeginning{$ENDIF});
    tmpStream.ReadBuffer(Header,2);

    if (Header[1] <> $FF) or (Header[2] <> $D8) then
      Raise Exception.Create(TeeMsg_DPIIdentifier);

    Stream.WriteBuffer(Header,2);
    FillChar(AP0,Sizeof(AP0),#0);

    AP0[1]:= $FF;
    AP0[2]:= $E0;
    AP0[4]:= Sizeof(AP0) - 2;
    AP0[5]:= Byte('J');AP0[6]:= Byte('F');AP0[7]:= Byte('I');AP0[8]:= Byte('F');
    AP0[10]:= 1; AP0[11]:= 2;
    AP0[12]:= 1;
    AP0[13]:= Rx shr 8; AP0[14]:= Rx and $FF;
    AP0[15]:= Ry shr 8; AP0[16]:= Ry and $FF;

    Stream.WriteBuffer(AP0,Sizeof(AP0));

    repeat
      tmpStream.ReadBuffer(Header,4);
      Size:= ((Header[3] shl 8) + Header[4]) - 2;
        case Header[2] of
          $FE, $E0..$EF: tmpStream.Seek(Size,{$IFDEF D8}soCurrent{$ELSE}soFromCurrent{$ENDIF});
          $01,$D0..$D9: begin
          Stream.WriteBuffer(Header,2);
          tmpStream.Seek(-2, {$IFDEF D8}soCurrent{$ELSE}soFromCurrent{$ENDIF})
        end
        else
        begin
          tmpStream.ReadBuffer(Buffer,Size);
          Stream.WriteBuffer(Header,4);
          Stream.WriteBuffer(Buffer,Size);
        end;
      end;
    until Header[2] = $DA;

    Stream.CopyFrom(tmpStream,tmpStream.Size - tmpStream.Position);
  finally
    tmpStream.Free;
  end;
end;

procedure TJPEGExportFormat.SetQuality(const Value: Integer);
begin
  Options;
  FProperties.UpDown1.Position:=Value;
end;

Destructor TJPEGExportFormat.Destroy;
begin
  FreeAndNil(FProperties);
  inherited;
end;

function TJPEGExportFormat.GraphicClass: TGraphicClass;
begin
  result:=TJPEGImage;
end;

initialization
  RegisterTeeExportFormat(TJPEGExportFormat);
finalization
  UnRegisterTeeExportFormat(TJPEGExportFormat);
end.
