{******************************************************************************}
{* This file is part of SAS.Planet project.                                   *}
{*                                                                            *}
{* Copyright (C) 2007-2022, SAS.Planet development team.                      *}
{*                                                                            *}
{* SAS.Planet is free software: you can redistribute it and/or modify         *}
{* it under the terms of the GNU General Public License as published by       *}
{* the Free Software Foundation, either version 3 of the License, or          *}
{* (at your option) any later version.                                        *}
{*                                                                            *}
{* SAS.Planet is distributed in the hope that it will be useful,              *}
{* but WITHOUT ANY WARRANTY; without even the implied warranty of             *}
{* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the               *}
{* GNU General Public License for more details.                               *}
{*                                                                            *}
{* You should have received a copy of the GNU General Public License          *}
{* along with SAS.Planet. If not, see <http://www.gnu.org/licenses/>.         *}
{*                                                                            *}
{* https://github.com/sasgis/sas.planet.src                                   *}
{******************************************************************************}

unit u_BitmapMapCombinerJPG;

interface

uses
  i_InternalPerformanceCounter,
  i_BitmapMapCombiner,
  i_RegionProcessParamsFrame,
  u_BitmapMapCombinerFactoryBase;

type
  TBitmapMapCombinerFactoryJPG = class(TBitmapMapCombinerFactoryBase)
  private
    FSaveRectCounter: IInternalPerformanceCounter;
    FPrepareDataCounter: IInternalPerformanceCounter;
    FGetLineCounter: IInternalPerformanceCounter;
  protected
    function PrepareMapCombiner(
      const AParams: IRegionProcessParamsFrameMapCombine;
      const AProgressInfo: IBitmapCombineProgressUpdate
    ): IBitmapMapCombiner; override;
  public
    constructor Create(
      const ACounterList: IInternalPerformanceCounterList
    );
  end;

implementation

uses
  Types,
  SysUtils,
  Classes,
  LibJpegWrite,
  gnugettext,
  Exif,
  t_GeoTypes,
  t_CommonTypes,
  t_MapCombineOptions,
  i_NotifierOperation,
  i_Projection,
  i_BitmapTileProvider,
  i_ImageLineProvider,
  u_BaseInterfacedObject,
  u_ImageLineProvider,
  u_GeoFunc,
  u_GlobalDllName,
  u_ResStrings;

const
  JPG_MAX_WIDTH = 65536;
  JPG_MAX_HEIGHT = 65536;

type
  TBitmapMapCombinerJPG = class(TBaseInterfacedObject, IBitmapMapCombiner)
  private
    FProgressUpdate: IBitmapCombineProgressUpdate;
    FWidth: Integer;
    FHeight: Integer;
    FQuality: Integer;
    FSaveRectCounter: IInternalPerformanceCounter;
    FPrepareDataCounter: IInternalPerformanceCounter;
    FGetLineCounter: IInternalPerformanceCounter;
    FLineProvider: IImageLineProvider;
    FSaveGeoRefInfoToExif: Boolean;
    FOperationID: Integer;
    FCancelNotifier: INotifierOperation;
    function GetLine(
      Sender: TObject;
      ALineNumber: Integer;
      ALineSize: Cardinal;
      out AAbort: Boolean
    ): PByte;
  private
    procedure SaveRect(
      AOperationID: Integer;
      const ACancelNotifier: INotifierOperation;
      const AFileName: string;
      const AImageProvider: IBitmapTileProvider;
      const AMapRect: TRect;
      const ACombinerCustomParams: IInterface
    );
  public
    constructor Create(
      const AProgressUpdate: IBitmapCombineProgressUpdate;
      const ASaveRectCounter: IInternalPerformanceCounter;
      const APrepareDataCounter: IInternalPerformanceCounter;
      const AGetLineCounter: IInternalPerformanceCounter;
      const AQuality: Integer;
      const ASaveGeoRefInfoToExif: Boolean
    );
  end;

{ TThreadMapCombineJPG }

constructor TBitmapMapCombinerJPG.Create(
  const AProgressUpdate: IBitmapCombineProgressUpdate;
  const ASaveRectCounter: IInternalPerformanceCounter;
  const APrepareDataCounter: IInternalPerformanceCounter;
  const AGetLineCounter: IInternalPerformanceCounter;
  const AQuality: Integer;
  const ASaveGeoRefInfoToExif: Boolean
);
begin
  inherited Create;
  FProgressUpdate := AProgressUpdate;
  FSaveRectCounter := ASaveRectCounter;
  FPrepareDataCounter := APrepareDataCounter;
  FGetLineCounter := AGetLineCounter;
  FQuality := AQuality;
  FSaveGeoRefInfoToExif := ASaveGeoRefInfoToExif;
end;

procedure TBitmapMapCombinerJPG.SaveRect(
  AOperationID: Integer;
  const ACancelNotifier: INotifierOperation;
  const AFileName: string;
  const AImageProvider: IBitmapTileProvider;
  const AMapRect: TRect;
  const ACombinerCustomParams: IInterface
);
var
  VJpegWriter: TJpegWriter;
  VStream: TFileStream;
  VCurrentPieceRect: TRect;
  VProjection: IProjection;
  VMapPieceSize: TPoint;
  VExif: TExifSimple;
  VCenterLonLat: TDoublePoint;
  VUseBGRAColorSpace: Boolean;
  VContext: TInternalPerformanceCounterContext;
begin
  FOperationID := AOperationID;
  FCancelNotifier := ACancelNotifier;

  VContext := FSaveRectCounter.StartOperation;
  try
    VProjection := AImageProvider.Projection;
    VCurrentPieceRect := AMapRect;
    VMapPieceSize := RectSize(VCurrentPieceRect);

    VUseBGRAColorSpace := True; // Available for libjpeg-turbo only

    if VUseBGRAColorSpace then begin
      FLineProvider :=
        TImageLineProviderBGRA.Create(
          FPrepareDataCounter,
          FGetLineCounter,
          AImageProvider,
          AMapRect
        );
    end else begin
      FLineProvider :=
        TImageLineProviderRGB.Create(
          FPrepareDataCounter,
          FGetLineCounter,
          AImageProvider,
          AMapRect
        );
    end;

    FWidth := VMapPieceSize.X;
    FHeight := VMapPieceSize.Y;

    if (FWidth >= JPG_MAX_WIDTH) or (FHeight >= JPG_MAX_HEIGHT) then begin
      raise Exception.CreateFmt(
        SAS_ERR_ImageResolutionIsTooHigh,
        ['JPEG', FWidth, JPG_MAX_WIDTH, FHeight, JPG_MAX_HEIGHT]
      );
    end;

    VStream := TFileStream.Create(AFileName, fmCreate);
    try
      VJpegWriter := TJpegWriter.Create(VStream, VUseBGRAColorSpace, GDllName.Jpeg62);
      try
        VJpegWriter.Width := FWidth;
        VJpegWriter.Height := FHeight;
        VJpegWriter.Quality := FQuality;
        VJpegWriter.AddCommentMarker('Created with SAS.Planet' + #0);
        if FSaveGeoRefInfoToExif then begin
          VCenterLonLat := VProjection.PixelPos2LonLat(CenterPoint(AMapRect));
          VExif := TExifSimple.Create(VCenterLonLat.Y, VCenterLonLat.X);
          try
            VJpegWriter.AddExifMarker(VExif.Stream);
          finally
            VExif.Free;
          end;
        end;
        VJpegWriter.Compress(Self.GetLine);
      finally
        VJpegWriter.Free;
      end;
    finally
      VStream.Free;
    end;
  finally
    FSaveRectCounter.FinishOperation(VContext);
  end;
end;

function TBitmapMapCombinerJPG.GetLine(
  Sender: TObject;
  ALineNumber: Integer;
  ALineSize: Cardinal;
  out AAbort: Boolean
): PByte;
begin
  if ALineNumber mod 256 = 0 then begin
    FProgressUpdate.Update(ALineNumber / FHeight);
  end;
  Result := FLineProvider.GetLine(FOperationID, FCancelNotifier, ALineNumber);
  AAbort := (Result = nil) or FCancelNotifier.IsOperationCanceled(FOperationID);
end;

{ TBitmapMapCombinerFactoryJPG }

constructor TBitmapMapCombinerFactoryJPG.Create(
  const ACounterList: IInternalPerformanceCounterList
);
var
  VCounterList: IInternalPerformanceCounterList;
begin
  inherited Create(
    Point(0, 0),
    Point(JPG_MAX_WIDTH, JPG_MAX_HEIGHT),
    stsUnicode,
    'jpg',
    gettext_NoExtract('JPEG (Joint Photographic Experts Group)'),
    [mcQuality, mcExif]
  );
  VCounterList := ACounterList.CreateAndAddNewSubList('JPEG');
  FSaveRectCounter := VCounterList.CreateAndAddNewCounter('SaveRect');
  FPrepareDataCounter := VCounterList.CreateAndAddNewCounter('PrepareData');
  FGetLineCounter := VCounterList.CreateAndAddNewCounter('GetLine');
end;

function TBitmapMapCombinerFactoryJPG.PrepareMapCombiner(
  const AParams: IRegionProcessParamsFrameMapCombine;
  const AProgressInfo: IBitmapCombineProgressUpdate
): IBitmapMapCombiner;
begin
  Result :=
    TBitmapMapCombinerJPG.Create(
      AProgressInfo,
      FSaveRectCounter,
      FPrepareDataCounter,
      FGetLineCounter,
      AParams.CustomOptions.Quality,
      AParams.CustomOptions.IsSaveGeoRefInfoToExif
    );
end;

end.
