mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-10-15 20:26:13 +02:00
944 lines
26 KiB
ObjectPascal
944 lines
26 KiB
ObjectPascal
{*******************************************************}
|
|
{ }
|
|
{ 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. |