|
@@ -1,944 +0,0 @@
|
|
-{*******************************************************}
|
|
|
|
-{ }
|
|
|
|
-{ Delphi Runtime Library }
|
|
|
|
-{ JPEG Image Compression/Decompression Unit }
|
|
|
|
-{ }
|
|
|
|
-{ Copyright (c) 1997 Borland International }
|
|
|
|
-{ Copyright (c) 1998 Jacques Nomssi Nzali }
|
|
|
|
-{ }
|
|
|
|
-{*******************************************************}
|
|
|
|
-
|
|
|
|
-unit jpeg;
|
|
|
|
-
|
|
|
|
-interface
|
|
|
|
-
|
|
|
|
-{$I jconfig.inc}
|
|
|
|
-
|
|
|
|
-{$ifndef Delphi_Stream}
|
|
|
|
- Define "Delphi_Stream" in jconfig.inc - deliberate syntax error.
|
|
|
|
-{$endif}
|
|
|
|
-
|
|
|
|
-uses Windows, SysUtils, Classes, Graphics;
|
|
|
|
-
|
|
|
|
-type
|
|
|
|
- TJPEGData = class(TSharedImage)
|
|
|
|
- private
|
|
|
|
- FData: TCustomMemoryStream;
|
|
|
|
- FHeight: Integer;
|
|
|
|
- FWidth: Integer;
|
|
|
|
- FGrayscale: Boolean;
|
|
|
|
- protected
|
|
|
|
- procedure FreeHandle; override;
|
|
|
|
- public
|
|
|
|
- destructor Destroy; override;
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- TJPEGQualityRange = 1..100; { 100 = best quality, 25 = pretty awful }
|
|
|
|
- TJPEGPerformance = (jpBestQuality, jpBestSpeed);
|
|
|
|
- TJPEGScale = (jsFullSize, jsHalf, jsQuarter, jsEighth);
|
|
|
|
- TJPEGPixelFormat = (jf24Bit, jf8Bit);
|
|
|
|
-
|
|
|
|
- TJPEGImage = class(TGraphic)
|
|
|
|
- private
|
|
|
|
- FImage: TJPEGData;
|
|
|
|
- FBitmap: TBitmap;
|
|
|
|
- FScaledWidth: Integer;
|
|
|
|
- FScaledHeight: Integer;
|
|
|
|
- FTempPal: HPalette;
|
|
|
|
- FSmoothing: Boolean;
|
|
|
|
- FGrayScale: Boolean;
|
|
|
|
- FPixelFormat: TJPEGPixelFormat;
|
|
|
|
- FQuality: TJPEGQualityRange;
|
|
|
|
- FProgressiveDisplay: Boolean;
|
|
|
|
- FProgressiveEncoding: Boolean;
|
|
|
|
- FPerformance: TJPEGPerformance;
|
|
|
|
- FScale: TJPEGScale;
|
|
|
|
- FNeedRecalc: Boolean;
|
|
|
|
- procedure CalcOutputDimensions;
|
|
|
|
- function GetBitmap: TBitmap;
|
|
|
|
- function GetGrayscale: Boolean;
|
|
|
|
- procedure SetGrayscale(Value: Boolean);
|
|
|
|
- procedure SetPerformance(Value: TJPEGPerformance);
|
|
|
|
- procedure SetPixelFormat(Value: TJPEGPixelFormat);
|
|
|
|
- procedure SetScale(Value: TJPEGScale);
|
|
|
|
- procedure SetSmoothing(Value: Boolean);
|
|
|
|
- protected
|
|
|
|
- procedure AssignTo(Dest: TPersistent); override;
|
|
|
|
- procedure Changed(Sender: TObject); override;
|
|
|
|
- procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;
|
|
|
|
- function Equals(Graphic: TGraphic): Boolean; override;
|
|
|
|
- procedure FreeBitmap;
|
|
|
|
- function GetEmpty: Boolean; override;
|
|
|
|
- function GetHeight: Integer; override;
|
|
|
|
- function GetPalette: HPALETTE; override;
|
|
|
|
- function GetWidth: Integer; override;
|
|
|
|
- procedure NewBitmap;
|
|
|
|
- procedure NewImage;
|
|
|
|
- procedure ReadData(Stream: TStream); override;
|
|
|
|
- procedure ReadStream(Size: Longint; Stream: TStream);
|
|
|
|
- procedure SetHeight(Value: Integer); override;
|
|
|
|
- procedure SetPalette(Value: HPalette); override;
|
|
|
|
- procedure SetWidth(Value: Integer); override;
|
|
|
|
- procedure WriteData(Stream: TStream); override;
|
|
|
|
- property Bitmap: TBitmap read GetBitmap; { volatile }
|
|
|
|
- public
|
|
|
|
- constructor Create; override;
|
|
|
|
- destructor Destroy; override;
|
|
|
|
- procedure Compress;
|
|
|
|
- procedure DIBNeeded;
|
|
|
|
- procedure JPEGNeeded;
|
|
|
|
- procedure Assign(Source: TPersistent); override;
|
|
|
|
- procedure LoadFromStream(Stream: TStream); override;
|
|
|
|
- procedure SaveToStream(Stream: TStream); override;
|
|
|
|
- procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle;
|
|
|
|
- APalette: HPALETTE); override;
|
|
|
|
- procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
|
|
|
|
- var APalette: HPALETTE); override;
|
|
|
|
-
|
|
|
|
- { Options affecting / reflecting compression and decompression behavior }
|
|
|
|
- property Grayscale: Boolean read GetGrayscale write SetGrayscale;
|
|
|
|
- property ProgressiveEncoding: Boolean read FProgressiveEncoding write FProgressiveEncoding;
|
|
|
|
-
|
|
|
|
- { Compression options }
|
|
|
|
- property CompressionQuality: TJPEGQualityRange read FQuality write FQuality;
|
|
|
|
-
|
|
|
|
- { Decompression options }
|
|
|
|
- property PixelFormat: TJPEGPixelFormat read FPixelFormat write SetPixelFormat;
|
|
|
|
- property ProgressiveDisplay: Boolean read FProgressiveDisplay write FProgressiveDisplay;
|
|
|
|
- property Performance: TJPEGPerformance read FPerformance write SetPerformance;
|
|
|
|
- property Scale: TJPEGScale read FScale write SetScale;
|
|
|
|
- property Smoothing: Boolean read FSmoothing write SetSmoothing;
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- TJPEGDefaults = record
|
|
|
|
- CompressionQuality: TJPEGQualityRange;
|
|
|
|
- Grayscale: Boolean;
|
|
|
|
- Performance: TJPEGPerformance;
|
|
|
|
- PixelFormat: TJPEGPixelFormat;
|
|
|
|
- ProgressiveDisplay: Boolean;
|
|
|
|
- ProgressiveEncoding: Boolean;
|
|
|
|
- Scale: TJPEGScale;
|
|
|
|
- Smoothing: Boolean;
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
-var { Default settings for all new TJPEGImage instances }
|
|
|
|
- JPEGDefaults: TJPEGDefaults = (
|
|
|
|
- CompressionQuality: 90;
|
|
|
|
- Grayscale: False;
|
|
|
|
- Performance: jpBestQuality;
|
|
|
|
- PixelFormat: jf24Bit; { initialized to match video mode }
|
|
|
|
- ProgressiveDisplay: False;
|
|
|
|
- ProgressiveEncoding: False;
|
|
|
|
- Scale: jsFullSize;
|
|
|
|
- Smoothing: True;
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
-implementation
|
|
|
|
-
|
|
|
|
-uses jconsts,
|
|
|
|
- jmorecfg, jerror, jpeglib, jcomapi, jdmaster, jdapistd,
|
|
|
|
- jdatadst, jcparam, jcapimin, jcapistd, jdapimin, jdatasrc;
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-{ The following types and external function declarations are used to
|
|
|
|
- call into functions of the Independent JPEG Group's (IJG) implementation
|
|
|
|
- of the JPEG image compression/decompression public standard. The IJG
|
|
|
|
- library's C source code is compiled into OBJ files and linked into
|
|
|
|
- the Delphi application. Only types and functions needed by this unit
|
|
|
|
- are declared; all IJG internal structures are stubbed out with
|
|
|
|
- generic pointers to reduce internal source code congestion.
|
|
|
|
-
|
|
|
|
- IJG source code copyright (C) 1991-1996, Thomas G. Lane. }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-{ Error handler }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-{ Progress monitor object }
|
|
|
|
-type
|
|
|
|
- new_progress_mgr_ptr = ^new_progress_mgr;
|
|
|
|
- new_progress_mgr = record
|
|
|
|
- pub : jpeg_progress_mgr;
|
|
|
|
- { extra Delphi info }
|
|
|
|
- instance: TJPEGImage; { ptr to current TJPEGImage object }
|
|
|
|
- last_pass: Integer;
|
|
|
|
- last_pct: Integer;
|
|
|
|
- last_time: Integer;
|
|
|
|
- last_scanline: Integer;
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- TJPEGContext = record
|
|
|
|
- err: jpeg_error_mgr;
|
|
|
|
- progress: new_progress_mgr;
|
|
|
|
- FinalDCT: J_DCT_METHOD;
|
|
|
|
- FinalTwoPassQuant: Boolean;
|
|
|
|
- FinalDitherMode: J_DITHER_MODE;
|
|
|
|
- case byte of
|
|
|
|
- 0: (common: jpeg_common_struct);
|
|
|
|
- 1: (d: jpeg_decompress_struct);
|
|
|
|
- 2: (c: jpeg_compress_struct);
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-type
|
|
|
|
- EJPEG = class(EInvalidGraphic);
|
|
|
|
-
|
|
|
|
-procedure InvalidOperation(const Msg: string); near;
|
|
|
|
-begin
|
|
|
|
- raise EInvalidGraphicOperation.Create(Msg);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure JpegError(cinfo: j_common_ptr);
|
|
|
|
-begin
|
|
|
|
- raise EJPEG.CreateFmt(sJPEGError,[cinfo^.err^.msg_code]);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure EmitMessage(cinfo: j_common_ptr; msg_level: Integer); far;
|
|
|
|
-begin
|
|
|
|
- { -- !! }
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure OutputMessage(cinfo: j_common_ptr); far;
|
|
|
|
-begin
|
|
|
|
- { -- !! }
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure FormatMessage(cinfo: j_common_ptr; var buffer: string); far;
|
|
|
|
-begin
|
|
|
|
- { -- !! }
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure ResetErrorMgr(cinfo: j_common_ptr);
|
|
|
|
-begin
|
|
|
|
- cinfo^.err^.num_warnings := 0;
|
|
|
|
- cinfo^.err^.msg_code := 0;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-const
|
|
|
|
- jpeg_std_error: jpeg_error_mgr = (
|
|
|
|
- error_exit: JpegError;
|
|
|
|
- emit_message: EmitMessage;
|
|
|
|
- output_message: OutputMessage;
|
|
|
|
- format_message: FormatMessage;
|
|
|
|
- reset_error_mgr: ResetErrorMgr);
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-{ TJPEGData }
|
|
|
|
-
|
|
|
|
-destructor TJPEGData.Destroy;
|
|
|
|
-begin
|
|
|
|
- FData.Free;
|
|
|
|
- inherited Destroy;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGData.FreeHandle;
|
|
|
|
-begin
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-{ TJPEGImage }
|
|
|
|
-
|
|
|
|
-constructor TJPEGImage.Create;
|
|
|
|
-begin
|
|
|
|
- inherited Create;
|
|
|
|
- NewImage;
|
|
|
|
- FQuality := JPEGDefaults.CompressionQuality;
|
|
|
|
- FGrayscale := JPEGDefaults.Grayscale;
|
|
|
|
- FPerformance := JPEGDefaults.Performance;
|
|
|
|
- FPixelFormat := JPEGDefaults.PixelFormat;
|
|
|
|
- FProgressiveDisplay := JPEGDefaults.ProgressiveDisplay;
|
|
|
|
- FProgressiveEncoding := JPEGDefaults.ProgressiveEncoding;
|
|
|
|
- FScale := JPEGDefaults.Scale;
|
|
|
|
- FSmoothing := JPEGDefaults.Smoothing;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-destructor TJPEGImage.Destroy;
|
|
|
|
-begin
|
|
|
|
- if FTempPal <> 0 then DeleteObject(FTempPal);
|
|
|
|
- FBitmap.Free;
|
|
|
|
- FImage.Release;
|
|
|
|
- inherited Destroy;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.Assign(Source: TPersistent);
|
|
|
|
-begin
|
|
|
|
- if Source is TJPEGImage then
|
|
|
|
- begin
|
|
|
|
- FImage.Release;
|
|
|
|
- FImage := TJPEGImage(Source).FImage;
|
|
|
|
- FImage.Reference;
|
|
|
|
- if TJPEGImage(Source).FBitmap <> nil then
|
|
|
|
- begin
|
|
|
|
- NewBitmap;
|
|
|
|
- FBitmap.Assign(TJPEGImage(Source).FBitmap);
|
|
|
|
- end;
|
|
|
|
- end
|
|
|
|
- else if Source is TBitmap then
|
|
|
|
- begin
|
|
|
|
- NewImage;
|
|
|
|
- NewBitmap;
|
|
|
|
- FBitmap.Assign(Source);
|
|
|
|
- end
|
|
|
|
- else
|
|
|
|
- inherited Assign(Source);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.AssignTo(Dest: TPersistent);
|
|
|
|
-begin
|
|
|
|
- if Dest is TBitmap then
|
|
|
|
- Dest.Assign(Bitmap)
|
|
|
|
- else
|
|
|
|
- inherited AssignTo(Dest);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure ProgressCallback(const cinfo: jpeg_common_struct);
|
|
|
|
-var
|
|
|
|
- Ticks: Integer;
|
|
|
|
- R: TRect;
|
|
|
|
- temp: Integer;
|
|
|
|
- progress : new_progress_mgr_ptr;
|
|
|
|
-begin
|
|
|
|
- progress := new_progress_mgr_ptr(cinfo.progress);
|
|
|
|
- if (progress = nil) or (progress.instance = nil) then Exit;
|
|
|
|
- with progress^,pub do
|
|
|
|
- begin
|
|
|
|
- Ticks := GetTickCount;
|
|
|
|
- if (Ticks - last_time) < 500 then Exit;
|
|
|
|
- temp := last_time;
|
|
|
|
- last_time := Ticks;
|
|
|
|
- if temp = 0 then Exit;
|
|
|
|
- if cinfo.is_decompressor then
|
|
|
|
- with j_decompress_ptr(@cinfo)^ do
|
|
|
|
- begin
|
|
|
|
- R := Rect(0, last_scanline, output_width, output_scanline);
|
|
|
|
- if R.Bottom < last_scanline then
|
|
|
|
- R.Bottom := output_height;
|
|
|
|
- end
|
|
|
|
- else
|
|
|
|
- R := Rect(0,0,0,0);
|
|
|
|
- temp := Trunc(100.0*(completed_passes + (pass_counter/pass_limit))/total_passes);
|
|
|
|
- if temp = last_pct then Exit;
|
|
|
|
- last_pct := temp;
|
|
|
|
- if cinfo.is_decompressor then
|
|
|
|
- last_scanline := j_decompress_ptr(@cinfo)^.output_scanline;
|
|
|
|
- instance.Progress(instance, psRunning, temp, (R.Bottom - R.Top) >= 4, R, '');
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure ReleaseContext(var jc: TJPEGContext);
|
|
|
|
-begin
|
|
|
|
- if jc.common.err = nil then Exit;
|
|
|
|
- jpeg_destroy(@jc.common);
|
|
|
|
- jc.common.err := nil;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure InitDecompressor(Obj: TJPEGImage; var jc: TJPEGContext);
|
|
|
|
-begin
|
|
|
|
- FillChar(jc, sizeof(jc), 0);
|
|
|
|
- jc.err := jpeg_std_error;
|
|
|
|
- jc.common.err := @jc.err;
|
|
|
|
-
|
|
|
|
- jpeg_CreateDecompress(@jc.d, JPEG_LIB_VERSION, sizeof(jc.d));
|
|
|
|
- with Obj do
|
|
|
|
- try
|
|
|
|
- jc.progress.pub.progress_monitor := @ProgressCallback;
|
|
|
|
- jc.progress.instance := Obj;
|
|
|
|
- jc.common.progress := @jc.progress;
|
|
|
|
-
|
|
|
|
- Obj.FImage.FData.Position := 0;
|
|
|
|
- jpeg_stdio_src(@jc.d, @FImage.FData);
|
|
|
|
- jpeg_read_header(@jc.d, TRUE);
|
|
|
|
-
|
|
|
|
- jc.d.scale_num := 1;
|
|
|
|
- jc.d.scale_denom := 1 shl Byte(FScale);
|
|
|
|
- jc.d.do_block_smoothing := FSmoothing;
|
|
|
|
-
|
|
|
|
- if FGrayscale then jc.d.out_color_space := JCS_GRAYSCALE;
|
|
|
|
- if (PixelFormat = jf8Bit) or (jc.d.out_color_space = JCS_GRAYSCALE) then
|
|
|
|
- begin
|
|
|
|
- jc.d.quantize_colors := True;
|
|
|
|
- jc.d.desired_number_of_colors := 236;
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- if FPerformance = jpBestSpeed then
|
|
|
|
- begin
|
|
|
|
- jc.d.dct_method := JDCT_IFAST;
|
|
|
|
- jc.d.two_pass_quantize := False;
|
|
|
|
- { jc.d.do_fancy_upsampling := False; !! AV inside jpeglib }
|
|
|
|
- jc.d.dither_mode := JDITHER_ORDERED;
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- jc.FinalDCT := jc.d.dct_method;
|
|
|
|
- jc.FinalTwoPassQuant := jc.d.two_pass_quantize;
|
|
|
|
- jc.FinalDitherMode := jc.d.dither_mode;
|
|
|
|
- if FProgressiveDisplay and jpeg_has_multiple_scans(@jc.d) then
|
|
|
|
- begin { save requested settings, reset for fastest on all but last scan }
|
|
|
|
- jc.d.enable_2pass_quant := jc.d.two_pass_quantize;
|
|
|
|
- jc.d.dct_method := JDCT_IFAST;
|
|
|
|
- jc.d.two_pass_quantize := False;
|
|
|
|
- jc.d.dither_mode := JDITHER_ORDERED;
|
|
|
|
- jc.d.buffered_image := True;
|
|
|
|
- end;
|
|
|
|
- except
|
|
|
|
- ReleaseContext(jc);
|
|
|
|
- raise;
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.CalcOutputDimensions;
|
|
|
|
-var
|
|
|
|
- jc: TJPEGContext;
|
|
|
|
-begin
|
|
|
|
- if not FNeedRecalc then Exit;
|
|
|
|
- InitDecompressor(Self, jc);
|
|
|
|
- try
|
|
|
|
- jc.common.progress := nil;
|
|
|
|
- jpeg_calc_output_dimensions(@jc.d);
|
|
|
|
- { read output dimensions }
|
|
|
|
- FScaledWidth := jc.d.output_width;
|
|
|
|
- FScaledHeight := jc.d.output_height;
|
|
|
|
- FProgressiveEncoding := jpeg_has_multiple_scans(@jc.d);
|
|
|
|
- finally
|
|
|
|
- ReleaseContext(jc);
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.Changed(Sender: TObject);
|
|
|
|
-begin
|
|
|
|
- inherited Changed(Sender);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.Compress;
|
|
|
|
-var
|
|
|
|
- LinesWritten, LinesPerCall: Integer;
|
|
|
|
- SrcScanLine: Pointer;
|
|
|
|
- PtrInc: Integer;
|
|
|
|
- jc: TJPEGContext;
|
|
|
|
- Src: TBitmap;
|
|
|
|
-begin
|
|
|
|
- FillChar(jc, sizeof(jc), 0);
|
|
|
|
- jc.err := jpeg_std_error;
|
|
|
|
- jc.common.err := @jc.err;
|
|
|
|
-
|
|
|
|
- jpeg_CreateCompress(@jc.c, JPEG_LIB_VERSION, sizeof(jc.c));
|
|
|
|
- try
|
|
|
|
- try
|
|
|
|
- jc.progress.pub.progress_monitor := @ProgressCallback;
|
|
|
|
- jc.progress.instance := Self;
|
|
|
|
- jc.common.progress := @jc.progress;
|
|
|
|
-
|
|
|
|
- if FImage.FData <> nil then NewImage;
|
|
|
|
- FImage.FData := TMemoryStream.Create;
|
|
|
|
- FImage.FData.Position := 0;
|
|
|
|
- jpeg_stdio_dest(@jc.c, @FImage.FData);
|
|
|
|
-
|
|
|
|
- if (FBitmap = nil) or (FBitmap.Width = 0) or (FBitmap.Height = 0) then Exit;
|
|
|
|
- jc.c.image_width := FBitmap.Width;
|
|
|
|
- FImage.FWidth := FBitmap.Width;
|
|
|
|
- jc.c.image_height := FBitmap.Height;
|
|
|
|
- FImage.FHeight := FBitmap.Height;
|
|
|
|
- jc.c.input_components := 3; { JPEG requires 24bit RGB input }
|
|
|
|
- jc.c.in_color_space := JCS_RGB;
|
|
|
|
-
|
|
|
|
- Src := TBitmap.Create;
|
|
|
|
- try
|
|
|
|
- Src.Assign(FBitmap);
|
|
|
|
- Src.PixelFormat := pf24bit;
|
|
|
|
-
|
|
|
|
- jpeg_set_defaults(@jc.c);
|
|
|
|
- jpeg_set_quality(@jc.c, FQuality, True);
|
|
|
|
-
|
|
|
|
- if FGrayscale then
|
|
|
|
- begin
|
|
|
|
- FImage.FGrayscale := True;
|
|
|
|
- jpeg_set_colorspace(@jc.c, JCS_GRAYSCALE);
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- if ProgressiveEncoding then
|
|
|
|
- jpeg_simple_progression(@jc.c);
|
|
|
|
-
|
|
|
|
- SrcScanline := Src.ScanLine[0];
|
|
|
|
- PtrInc := Integer(Src.ScanLine[1]) - Integer(SrcScanline);
|
|
|
|
-
|
|
|
|
- { if no dword padding required and source bitmap is top-down }
|
|
|
|
- if (PtrInc > 0) and ((PtrInc and 3) = 0) then
|
|
|
|
- LinesPerCall := jc.c.image_height { do whole bitmap in one call }
|
|
|
|
- else
|
|
|
|
- LinesPerCall := 1; { otherwise spoonfeed one row at a time }
|
|
|
|
-
|
|
|
|
- Progress(Self, psStarting, 0, False, Rect(0,0,0,0), '');
|
|
|
|
- try
|
|
|
|
- jpeg_start_compress(@jc.c, True);
|
|
|
|
-
|
|
|
|
- while (jc.c.next_scanline < jc.c.image_height) do
|
|
|
|
- begin
|
|
|
|
- LinesWritten := jpeg_write_scanlines(@jc.c, @SrcScanline, LinesPerCall);
|
|
|
|
- Inc(Integer(SrcScanline), PtrInc * LinesWritten);
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- jpeg_finish_compress(@jc.c);
|
|
|
|
- finally
|
|
|
|
- if ExceptObject = nil then
|
|
|
|
- PtrInc := 100
|
|
|
|
- else
|
|
|
|
- PtrInc := 0;
|
|
|
|
- Progress(Self, psEnding, PtrInc, False, Rect(0,0,0,0), '');
|
|
|
|
- end;
|
|
|
|
- finally
|
|
|
|
- Src.Free;
|
|
|
|
- end;
|
|
|
|
- except
|
|
|
|
- on EAbort do { OnProgress can raise EAbort to cancel image save }
|
|
|
|
- NewImage; { Throw away any partial jpg data }
|
|
|
|
- end;
|
|
|
|
- finally
|
|
|
|
- ReleaseContext(jc);
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.DIBNeeded;
|
|
|
|
-begin
|
|
|
|
- GetBitmap;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.Draw(ACanvas: TCanvas; const Rect: TRect);
|
|
|
|
-begin
|
|
|
|
- ACanvas.StretchDraw(Rect, Bitmap);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-function TJPEGImage.Equals(Graphic: TGraphic): Boolean;
|
|
|
|
-begin
|
|
|
|
- Result := (Graphic is TJPEGImage) and
|
|
|
|
- (FImage = TJPEGImage(Graphic).FImage); { ---!! }
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.FreeBitmap;
|
|
|
|
-begin
|
|
|
|
- FBitmap.Free;
|
|
|
|
- FBitmap := nil;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-function BuildPalette(const cinfo: jpeg_decompress_struct): HPalette;
|
|
|
|
-var
|
|
|
|
- Pal: TMaxLogPalette;
|
|
|
|
- I: Integer;
|
|
|
|
- C: Byte;
|
|
|
|
-begin
|
|
|
|
- Pal.palVersion := $300;
|
|
|
|
- Pal.palNumEntries := cinfo.actual_number_of_colors;
|
|
|
|
- if cinfo.out_color_space = JCS_GRAYSCALE then
|
|
|
|
- for I := 0 to Pal.palNumEntries-1 do
|
|
|
|
- begin
|
|
|
|
- C := cinfo.colormap^[0]^[I];
|
|
|
|
- Pal.palPalEntry[I].peRed := C;
|
|
|
|
- Pal.palPalEntry[I].peGreen := C;
|
|
|
|
- Pal.palPalEntry[I].peBlue := C;
|
|
|
|
- Pal.palPalEntry[I].peFlags := 0;
|
|
|
|
- end
|
|
|
|
- else
|
|
|
|
- for I := 0 to Pal.palNumEntries-1 do
|
|
|
|
- begin
|
|
|
|
- Pal.palPalEntry[I].peRed := cinfo.colormap^[2]^[I];
|
|
|
|
- Pal.palPalEntry[I].peGreen := cinfo.colormap^[1]^[I];
|
|
|
|
- Pal.palPalEntry[I].peBlue := cinfo.colormap^[0]^[I];
|
|
|
|
- Pal.palPalEntry[I].peFlags := 0;
|
|
|
|
- end;
|
|
|
|
- Result := CreatePalette(PLogPalette(@Pal)^);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure BuildColorMap(var cinfo: jpeg_decompress_struct; P: HPalette);
|
|
|
|
-var
|
|
|
|
- Pal: TMaxLogPalette;
|
|
|
|
- Count, I: Integer;
|
|
|
|
-begin
|
|
|
|
- Count := GetPaletteEntries(P, 0, 256, Pal.palPalEntry);
|
|
|
|
- if Count = 0 then Exit; { jpeg_destroy will free colormap }
|
|
|
|
- cinfo.colormap := cinfo.mem.alloc_sarray(j_common_ptr(@cinfo), JPOOL_IMAGE, Count, 3);
|
|
|
|
- cinfo.actual_number_of_colors := Count;
|
|
|
|
- for I := 0 to Count-1 do
|
|
|
|
- begin
|
|
|
|
- Byte(cinfo.colormap^[2]^[I]) := Pal.palPalEntry[I].peRed;
|
|
|
|
- Byte(cinfo.colormap^[1]^[I]) := Pal.palPalEntry[I].peGreen;
|
|
|
|
- Byte(cinfo.colormap^[0]^[I]) := Pal.palPalEntry[I].peBlue;
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-function TJPEGImage.GetBitmap: TBitmap;
|
|
|
|
-var
|
|
|
|
- LinesPerCall, LinesRead: Integer;
|
|
|
|
- DestScanLine: Pointer;
|
|
|
|
- PtrInc: Integer;
|
|
|
|
- jc: TJPEGContext;
|
|
|
|
- GeneratePalette: Boolean;
|
|
|
|
-begin
|
|
|
|
- Result := FBitmap;
|
|
|
|
- if Result <> nil then Exit;
|
|
|
|
- if (FBitmap = nil) then FBitmap := TBitmap.Create;
|
|
|
|
- Result := FBitmap;
|
|
|
|
- GeneratePalette := True;
|
|
|
|
-
|
|
|
|
- InitDecompressor(Self, jc);
|
|
|
|
- try
|
|
|
|
- try
|
|
|
|
- { Set the bitmap pixel format }
|
|
|
|
- FBitmap.Handle := 0;
|
|
|
|
- if (PixelFormat = jf8Bit) or (jc.d.out_color_space = JCS_GRAYSCALE) then
|
|
|
|
- FBitmap.PixelFormat := pf8bit
|
|
|
|
- else
|
|
|
|
- FBitmap.PixelFormat := pf24bit;
|
|
|
|
-
|
|
|
|
- Progress(Self, psStarting, 0, False, Rect(0,0,0,0), '');
|
|
|
|
- try
|
|
|
|
- if (FTempPal <> 0) then
|
|
|
|
- begin
|
|
|
|
- if (FPixelFormat = jf8Bit) then
|
|
|
|
- begin { Generate DIB using assigned palette }
|
|
|
|
- BuildColorMap(jc.d, FTempPal);
|
|
|
|
- FBitmap.Palette := CopyPalette(FTempPal); { Keep FTempPal around }
|
|
|
|
- GeneratePalette := False;
|
|
|
|
- end
|
|
|
|
- else
|
|
|
|
- begin
|
|
|
|
- DeleteObject(FTempPal);
|
|
|
|
- FTempPal := 0;
|
|
|
|
- end;
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- jpeg_start_decompress(@jc.d);
|
|
|
|
-
|
|
|
|
- { Set bitmap width and height }
|
|
|
|
- with FBitmap do
|
|
|
|
- begin
|
|
|
|
- Handle := 0;
|
|
|
|
- Width := jc.d.output_width;
|
|
|
|
- Height := jc.d.output_height;
|
|
|
|
- DestScanline := ScanLine[0];
|
|
|
|
- PtrInc := Integer(ScanLine[1]) - Integer(DestScanline);
|
|
|
|
- if (PtrInc > 0) and ((PtrInc and 3) = 0) then
|
|
|
|
- { if no dword padding is required and output bitmap is top-down }
|
|
|
|
- LinesPerCall := jc.d.rec_outbuf_height { read multiple rows per call }
|
|
|
|
- else
|
|
|
|
- LinesPerCall := 1; { otherwise read one row at a time }
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- if jc.d.buffered_image then
|
|
|
|
- begin { decode progressive scans at low quality, high speed }
|
|
|
|
- while jpeg_consume_input(@jc.d) <> JPEG_REACHED_EOI do
|
|
|
|
- begin
|
|
|
|
- jpeg_start_output(@jc.d, jc.d.input_scan_number);
|
|
|
|
- { extract color palette }
|
|
|
|
- if (jc.common.progress^.completed_passes = 0) and (jc.d.colormap <> nil)
|
|
|
|
- and (FBitmap.PixelFormat = pf8bit) and GeneratePalette then
|
|
|
|
- begin
|
|
|
|
- FBitmap.Palette := BuildPalette(jc.d);
|
|
|
|
- PaletteModified := True;
|
|
|
|
- end;
|
|
|
|
- DestScanLine := FBitmap.ScanLine[0];
|
|
|
|
- while (jc.d.output_scanline < jc.d.output_height) do
|
|
|
|
- begin
|
|
|
|
- LinesRead := jpeg_read_scanlines(@jc.d, @DestScanline, LinesPerCall);
|
|
|
|
- Inc(Integer(DestScanline), PtrInc * LinesRead);
|
|
|
|
- end;
|
|
|
|
- jpeg_finish_output(@jc.d);
|
|
|
|
- end;
|
|
|
|
- { reset options for final pass at requested quality }
|
|
|
|
- jc.d.dct_method := jc.FinalDCT;
|
|
|
|
- jc.d.dither_mode := jc.FinalDitherMode;
|
|
|
|
- if jc.FinalTwoPassQuant then
|
|
|
|
- begin
|
|
|
|
- jc.d.two_pass_quantize := True;
|
|
|
|
- jc.d.colormap := nil;
|
|
|
|
- end;
|
|
|
|
- jpeg_start_output(@jc.d, jc.d.input_scan_number);
|
|
|
|
- DestScanLine := FBitmap.ScanLine[0];
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- { build final color palette }
|
|
|
|
- if (not jc.d.buffered_image or jc.FinalTwoPassQuant) and
|
|
|
|
- (jc.d.colormap <> nil) and GeneratePalette then
|
|
|
|
- begin
|
|
|
|
- FBitmap.Palette := BuildPalette(jc.d);
|
|
|
|
- PaletteModified := True;
|
|
|
|
- DestScanLine := FBitmap.ScanLine[0];
|
|
|
|
- end;
|
|
|
|
- { final image pass for progressive, first and only pass for baseline }
|
|
|
|
- while (jc.d.output_scanline < jc.d.output_height) do
|
|
|
|
- begin
|
|
|
|
- LinesRead := jpeg_read_scanlines(@jc.d, @DestScanline, LinesPerCall);
|
|
|
|
- Inc(Integer(DestScanline), PtrInc * LinesRead);
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- if jc.d.buffered_image then jpeg_finish_output(@jc.d);
|
|
|
|
- jpeg_finish_decompress(@jc.d);
|
|
|
|
- finally
|
|
|
|
- if ExceptObject = nil then
|
|
|
|
- PtrInc := 100
|
|
|
|
- else
|
|
|
|
- PtrInc := 0;
|
|
|
|
- Progress(Self, psEnding, PtrInc, PaletteModified, Rect(0,0,0,0), '');
|
|
|
|
- { Make sure new palette gets realized, in case OnProgress event didn't. }
|
|
|
|
- if PaletteModified then
|
|
|
|
- Changed(Self);
|
|
|
|
- end;
|
|
|
|
- except
|
|
|
|
- on EAbort do ; { OnProgress can raise EAbort to cancel image load }
|
|
|
|
- end;
|
|
|
|
- finally
|
|
|
|
- ReleaseContext(jc);
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-function TJPEGImage.GetEmpty: Boolean;
|
|
|
|
-begin
|
|
|
|
- Result := (FImage.FData = nil) and FBitmap.Empty;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-function TJPEGImage.GetGrayscale: Boolean;
|
|
|
|
-begin
|
|
|
|
- Result := FGrayscale or FImage.FGrayscale;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-function TJPEGImage.GetPalette: HPalette;
|
|
|
|
-var
|
|
|
|
- DC: HDC;
|
|
|
|
-begin
|
|
|
|
- Result := 0;
|
|
|
|
- if FBitmap <> nil then
|
|
|
|
- Result := FBitmap.Palette
|
|
|
|
- else if FTempPal <> 0 then
|
|
|
|
- Result := FTempPal
|
|
|
|
- else if FPixelFormat = jf24Bit then { check for 8 bit screen }
|
|
|
|
- begin
|
|
|
|
- DC := GetDC(0);
|
|
|
|
- if (GetDeviceCaps(DC, BITSPIXEL) * GetDeviceCaps(DC, PLANES)) <= 8 then
|
|
|
|
- begin
|
|
|
|
- if FTempPal <> 0 then DeleteObject(FTempPal); { Memory leak -- fix }
|
|
|
|
- FTempPal := CreateHalftonePalette(DC);
|
|
|
|
- Result := FTempPal;
|
|
|
|
- end;
|
|
|
|
- ReleaseDC(0, DC);
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-function TJPEGImage.GetHeight: Integer;
|
|
|
|
-begin
|
|
|
|
- if FBitmap <> nil then
|
|
|
|
- Result := FBitmap.Height
|
|
|
|
- else if FScale = jsFullSize then
|
|
|
|
- Result := FImage.FHeight
|
|
|
|
- else
|
|
|
|
- begin
|
|
|
|
- CalcOutputDimensions;
|
|
|
|
- Result := FScaledHeight;
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-function TJPEGImage.GetWidth: Integer;
|
|
|
|
-begin
|
|
|
|
- if FBitmap <> nil then
|
|
|
|
- Result := FBitmap.Width
|
|
|
|
- else if FScale = jsFullSize then
|
|
|
|
- Result := FImage.FWidth
|
|
|
|
- else
|
|
|
|
- begin
|
|
|
|
- CalcOutputDimensions;
|
|
|
|
- Result := FScaledWidth;
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.JPEGNeeded;
|
|
|
|
-begin
|
|
|
|
- if FImage.FData = nil then
|
|
|
|
- Compress;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.LoadFromClipboardFormat(AFormat: Word; AData: THandle;
|
|
|
|
- APalette: HPALETTE);
|
|
|
|
-begin
|
|
|
|
- { --!! check for jpeg clipboard data, mime type image/jpeg }
|
|
|
|
- FBitmap.LoadFromClipboardFormat(AFormat, AData, APalette);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.LoadFromStream(Stream: TStream);
|
|
|
|
-begin
|
|
|
|
- ReadStream(Stream.Size - Stream.Position, Stream);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.NewBitmap;
|
|
|
|
-begin
|
|
|
|
- FBitmap.Free;
|
|
|
|
- FBitmap := TBitmap.Create;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.NewImage;
|
|
|
|
-begin
|
|
|
|
- if FImage <> nil then FImage.Release;
|
|
|
|
- FImage := TJPEGData.Create;
|
|
|
|
- FImage.Reference;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.ReadData(Stream: TStream);
|
|
|
|
-var
|
|
|
|
- Size: Longint;
|
|
|
|
-begin
|
|
|
|
- Stream.Read(Size, SizeOf(Size));
|
|
|
|
- ReadStream(Size, Stream);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.ReadStream(Size: Longint; Stream: TStream);
|
|
|
|
-var
|
|
|
|
- jerr: jpeg_error_mgr;
|
|
|
|
- cinfo: jpeg_decompress_struct;
|
|
|
|
-begin
|
|
|
|
- NewImage;
|
|
|
|
- with FImage do
|
|
|
|
- begin
|
|
|
|
- FData := TMemoryStream.Create;
|
|
|
|
- FData.Size := Size;
|
|
|
|
- Stream.ReadBuffer(FData.Memory^, Size);
|
|
|
|
- if Size > 0 then
|
|
|
|
- begin
|
|
|
|
- jerr := jpeg_std_error; { use local var for thread isolation }
|
|
|
|
- cinfo.err := @jerr;
|
|
|
|
- jpeg_CreateDecompress(@cinfo, JPEG_LIB_VERSION, sizeof(cinfo));
|
|
|
|
- try
|
|
|
|
- FData.Position := 0;
|
|
|
|
- jpeg_stdio_src(@cinfo, @FData);
|
|
|
|
- jpeg_read_header(@cinfo, TRUE);
|
|
|
|
- FWidth := cinfo.image_width;
|
|
|
|
- FHeight := cinfo.image_height;
|
|
|
|
- FGrayscale := cinfo.jpeg_color_space = JCS_GRAYSCALE;
|
|
|
|
- FProgressiveEncoding := jpeg_has_multiple_scans(@cinfo);
|
|
|
|
- finally
|
|
|
|
- jpeg_destroy_decompress(@cinfo);
|
|
|
|
- end;
|
|
|
|
- end;
|
|
|
|
- end;
|
|
|
|
- PaletteModified := True;
|
|
|
|
- Changed(Self);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
|
|
|
|
- var APalette: HPALETTE);
|
|
|
|
-begin
|
|
|
|
-{ --!! check for jpeg clipboard format, mime type image/jpeg }
|
|
|
|
- Bitmap.SaveToClipboardFormat(AFormat, AData, APalette);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SaveToStream(Stream: TStream);
|
|
|
|
-begin
|
|
|
|
- JPEGNeeded;
|
|
|
|
- with FImage.FData do
|
|
|
|
- Stream.Write(Memory^, Size);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SetGrayscale(Value: Boolean);
|
|
|
|
-begin
|
|
|
|
- if FGrayscale <> Value then
|
|
|
|
- begin
|
|
|
|
- FreeBitmap;
|
|
|
|
- FGrayscale := Value;
|
|
|
|
- PaletteModified := True;
|
|
|
|
- Changed(Self);
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SetHeight(Value: Integer);
|
|
|
|
-begin
|
|
|
|
- InvalidOperation(SChangeJPGSize);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SetPalette(Value: HPalette);
|
|
|
|
-var
|
|
|
|
- SignalChange: Boolean;
|
|
|
|
-begin
|
|
|
|
- if Value <> FTempPal then
|
|
|
|
- begin
|
|
|
|
- SignalChange := (FBitmap <> nil) and (Value <> FBitmap.Palette);
|
|
|
|
- if SignalChange then FreeBitmap;
|
|
|
|
- FTempPal := Value;
|
|
|
|
- if SignalChange then
|
|
|
|
- begin
|
|
|
|
- PaletteModified := True;
|
|
|
|
- Changed(Self);
|
|
|
|
- end;
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SetPerformance(Value: TJPEGPerformance);
|
|
|
|
-begin
|
|
|
|
- if FPerformance <> Value then
|
|
|
|
- begin
|
|
|
|
- FreeBitmap;
|
|
|
|
- FPerformance := Value;
|
|
|
|
- PaletteModified := True;
|
|
|
|
- Changed(Self);
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SetPixelFormat(Value: TJPEGPixelFormat);
|
|
|
|
-begin
|
|
|
|
- if FPixelFormat <> Value then
|
|
|
|
- begin
|
|
|
|
- FreeBitmap;
|
|
|
|
- FPixelFormat := Value;
|
|
|
|
- PaletteModified := True;
|
|
|
|
- Changed(Self);
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SetScale(Value: TJPEGScale);
|
|
|
|
-begin
|
|
|
|
- if FScale <> Value then
|
|
|
|
- begin
|
|
|
|
- FreeBitmap;
|
|
|
|
- FScale := Value;
|
|
|
|
- FNeedRecalc := True;
|
|
|
|
- Changed(Self);
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SetSmoothing(Value: Boolean);
|
|
|
|
-begin
|
|
|
|
- if FSmoothing <> Value then
|
|
|
|
- begin
|
|
|
|
- FreeBitmap;
|
|
|
|
- FSmoothing := Value;
|
|
|
|
- Changed(Self);
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.SetWidth(Value: Integer);
|
|
|
|
-begin
|
|
|
|
- InvalidOperation(SChangeJPGSize);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure TJPEGImage.WriteData(Stream: TStream);
|
|
|
|
-var
|
|
|
|
- Size: Longint;
|
|
|
|
-begin
|
|
|
|
- Size := 0;
|
|
|
|
- if Assigned(FImage.FData) then Size := FImage.FData.Size;
|
|
|
|
- Stream.Write(Size, Sizeof(Size));
|
|
|
|
- if Size > 0 then Stream.Write(FImage.FData.Memory^, Size);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-procedure InitDefaults;
|
|
|
|
-var
|
|
|
|
- DC: HDC;
|
|
|
|
-begin
|
|
|
|
- DC := GetDC(0);
|
|
|
|
- if (GetDeviceCaps(DC, BITSPIXEL) * GetDeviceCaps(DC, PLANES)) <= 8 then
|
|
|
|
- JPEGDefaults.PixelFormat := jf8Bit
|
|
|
|
- else
|
|
|
|
- JPEGDefaults.PixelFormat := jf24Bit;
|
|
|
|
- ReleaseDC(0, DC);
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
-initialization
|
|
|
|
- InitDefaults;
|
|
|
|
- TPicture.RegisterFileFormat('jpg', 'JPEG Image File', TJPEGImage);
|
|
|
|
- TPicture.RegisterFileFormat('jpeg', 'JPEG Image File', TJPEGImage);
|
|
|
|
-finalization
|
|
|
|
- TPicture.UnregisterGraphicClass(TJPEGImage);
|
|
|
|
-end.
|
|
|