750 lines
22 KiB
Plaintext
750 lines
22 KiB
Plaintext
{
|
|
Vampyre Imaging Library
|
|
by Marek Mauder
|
|
https://github.com/galfar/imaginglib
|
|
https://imaginglib.sourceforge.io
|
|
- - - - -
|
|
This Source Code Form is subject to the terms of the Mozilla Public
|
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
file, You can obtain one at https://mozilla.org/MPL/2.0.
|
|
}
|
|
|
|
{ This unit contains image format loader/saver for Jpeg images.}
|
|
unit ImagingJpeg;
|
|
|
|
{$I ImagingOptions.inc}
|
|
|
|
{ You can choose which Pascal JpegLib implementation will be used.
|
|
IMJPEGLIB is version bundled with Imaging which works with all supported
|
|
compilers and platforms.
|
|
PASJPEG is original JpegLib translation or version modified for FPC
|
|
(and shipped with it). You can use PASJPEG if this version is already
|
|
linked with another part of your program and you don't want to have
|
|
two quite large almost the same libraries linked to your exe.
|
|
This is the case with Lazarus applications for example.}
|
|
|
|
{$DEFINE IMJPEGLIB}
|
|
{ $DEFINE PASJPEG}
|
|
|
|
{ Automatically use FPC's PasJpeg when compiling with Lazarus. }
|
|
{$IF Defined(LCL)}
|
|
{$UNDEF IMJPEGLIB}
|
|
{$DEFINE PASJPEG}
|
|
{$IFEND}
|
|
|
|
{ We usually want to skip the rest of the corrupted file when loading JPEG files
|
|
instead of getting exception. JpegLib's error handler can only be
|
|
exited using setjmp/longjmp ("non-local goto") functions to get error
|
|
recovery when loading corrupted JPEG files. This is implemented in assembler
|
|
and currently available only for 32bit Delphi targets and FPC.}
|
|
{$DEFINE ErrorJmpRecovery}
|
|
{$IF Defined(DCC) and not Defined(CPUX86)}
|
|
{$UNDEF ErrorJmpRecovery}
|
|
{$IFEND}
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, ImagingTypes, Imaging, ImagingColors,
|
|
{$IF Defined(IMJPEGLIB)}
|
|
imjpeglib, imjmorecfg, imjcomapi, imjdapimin, imjdeferr, imjerror,
|
|
imjdapistd, imjcapimin, imjcapistd, imjdmarker, imjcparam,
|
|
{$ELSEIF Defined(PASJPEG)}
|
|
jpeglib, jmorecfg, jcomapi, jdapimin, jdeferr, jerror,
|
|
jdapistd, jcapimin, jcapistd, jdmarker, jcparam,
|
|
{$IFEND}
|
|
ImagingUtility;
|
|
|
|
{$IF Defined(FPC) and Defined(PASJPEG)}
|
|
{ When using FPC's pasjpeg the channel order is BGR instead of RGB.
|
|
See RGB_RED_IS_0 in jconfig.inc. }
|
|
{$DEFINE RGBSWAPPED}
|
|
{$IFEND}
|
|
|
|
type
|
|
{ Class for loading/saving Jpeg images. Supports load/save of
|
|
8 bit grayscale and 24 bit RGB images. Jpegs can be saved with optional
|
|
progressive encoding.
|
|
Based on IJG's JpegLib so doesn't support alpha channels and lossless
|
|
coding.}
|
|
TJpegFileFormat = class(TImageFileFormat)
|
|
private
|
|
FGrayScale: Boolean;
|
|
protected
|
|
FQuality: LongInt;
|
|
FProgressive: LongBool;
|
|
procedure SetJpegIO(const JpegIO: TIOFunctions); virtual;
|
|
procedure Define; override;
|
|
function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
|
|
OnlyFirstLevel: Boolean): Boolean; override;
|
|
function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
|
|
Index: LongInt): Boolean; override;
|
|
procedure ConvertToSupported(var Image: TImageData;
|
|
const Info: TImageFormatInfo); override;
|
|
public
|
|
function TestFormat(Handle: TImagingHandle): Boolean; override;
|
|
procedure CheckOptionsValidity; override;
|
|
published
|
|
{ Controls Jpeg save compression quality. It is number in range 1..100.
|
|
1 means small/ugly file, 100 means large/nice file. Accessible trough
|
|
ImagingJpegQuality option.}
|
|
property Quality: LongInt read FQuality write FQuality;
|
|
{ If True Jpeg images are saved in progressive format. Accessible trough
|
|
ImagingJpegProgressive option.}
|
|
property Progressive: LongBool read FProgressive write FProgressive;
|
|
end;
|
|
|
|
implementation
|
|
|
|
const
|
|
SJpegFormatName = 'Joint Photographic Experts Group Image';
|
|
SJpegMasks = '*.jpg,*.jpeg,*.jfif,*.jpe,*.jif';
|
|
JpegSupportedFormats: TImageFormats = [ifR8G8B8, ifGray8];
|
|
JpegDefaultQuality = 90;
|
|
JpegDefaultProgressive = False;
|
|
|
|
const
|
|
{ Jpeg file identifiers.}
|
|
JpegMagic: TChar2 = #$FF#$D8;
|
|
BufferSize = 16384;
|
|
|
|
resourcestring
|
|
SJpegError = 'JPEG Error';
|
|
|
|
type
|
|
TJpegContext = record
|
|
case Byte of
|
|
0: (common: jpeg_common_struct);
|
|
1: (d: jpeg_decompress_struct);
|
|
2: (c: jpeg_compress_struct);
|
|
end;
|
|
|
|
TSourceMgr = record
|
|
Pub: jpeg_source_mgr;
|
|
Input: TImagingHandle;
|
|
Buffer: JOCTETPTR;
|
|
StartOfFile: Boolean;
|
|
end;
|
|
PSourceMgr = ^TSourceMgr;
|
|
|
|
TDestMgr = record
|
|
Pub: jpeg_destination_mgr;
|
|
Output: TImagingHandle;
|
|
Buffer: JOCTETPTR;
|
|
end;
|
|
PDestMgr = ^TDestMgr;
|
|
|
|
var
|
|
JIO: TIOFunctions;
|
|
JpegErrorMgr: jpeg_error_mgr;
|
|
|
|
{ Internal unit jpeglib support functions }
|
|
|
|
{$IFDEF ErrorJmpRecovery}
|
|
{$IFDEF DCC}
|
|
type
|
|
jmp_buf = record
|
|
EBX,
|
|
ESI,
|
|
EDI,
|
|
ESP,
|
|
EBP,
|
|
EIP: UInt32;
|
|
end;
|
|
pjmp_buf = ^jmp_buf;
|
|
|
|
{ JmpLib SetJmp/LongJmp Library
|
|
(C)Copyright 2003, 2004 Will DeWitt Jr. <edge@boink.net> }
|
|
function SetJmp(out jmpb: jmp_buf): Integer;
|
|
asm
|
|
{ -> EAX jmpb }
|
|
{ <- EAX Result }
|
|
MOV EDX, [ESP] // Fetch return address (EIP)
|
|
// Save task state
|
|
MOV [EAX+jmp_buf.&EBX], EBX
|
|
MOV [EAX+jmp_buf.&ESI], ESI
|
|
MOV [EAX+jmp_buf.&EDI], EDI
|
|
MOV [EAX+jmp_buf.&ESP], ESP
|
|
MOV [EAX+jmp_buf.&EBP], EBP
|
|
MOV [EAX+jmp_buf.&EIP], EDX
|
|
|
|
SUB EAX, EAX
|
|
@@1:
|
|
end;
|
|
|
|
procedure LongJmp(const jmpb: jmp_buf; retval: Integer);
|
|
asm
|
|
{ -> EAX jmpb }
|
|
{ EDX retval }
|
|
{ <- EAX Result }
|
|
XCHG EDX, EAX
|
|
|
|
MOV ECX, [EDX+jmp_buf.&EIP]
|
|
// Restore task state
|
|
MOV EBX, [EDX+jmp_buf.&EBX]
|
|
MOV ESI, [EDX+jmp_buf.&ESI]
|
|
MOV EDI, [EDX+jmp_buf.&EDI]
|
|
MOV ESP, [EDX+jmp_buf.&ESP]
|
|
MOV EBP, [EDX+jmp_buf.&EBP]
|
|
MOV [ESP], ECX // Restore return address (EIP)
|
|
|
|
TEST EAX, EAX // Ensure retval is <> 0
|
|
JNZ @@1
|
|
MOV EAX, 1
|
|
@@1:
|
|
end;
|
|
{$ENDIF}
|
|
|
|
type
|
|
TJmpBuf = jmp_buf;
|
|
TErrorClientData = record
|
|
JmpBuf: TJmpBuf;
|
|
ScanlineReadReached: Boolean;
|
|
end;
|
|
PErrorClientData = ^TErrorClientData;
|
|
{$ENDIF}
|
|
|
|
procedure JpegError(CInfo: j_common_ptr);
|
|
|
|
procedure RaiseError;
|
|
var
|
|
Buffer: AnsiString;
|
|
begin
|
|
// Create the message and raise exception
|
|
CInfo.err.format_message(CInfo, Buffer);
|
|
// Warning: you can get "Invalid argument index in format" exception when
|
|
// using FPC (see http://bugs.freepascal.org/view.php?id=21229).
|
|
// Fixed in FPC 2.7.1
|
|
{$IF Defined(FPC) and (FPC_FULLVERSION <= 20701)}
|
|
raise EImagingError.CreateFmt(SJPEGError + ' %d', [CInfo.err.msg_code]);
|
|
{$ELSE}
|
|
raise EImagingError.CreateFmt(SJPEGError + ' %d: ' + string(Buffer), [CInfo.err.msg_code]);
|
|
{$IFEND}
|
|
end;
|
|
|
|
begin
|
|
{$IFDEF ErrorJmpRecovery}
|
|
// Only recovers on loads and when header is successfully loaded
|
|
// (error occurs when reading scanlines)
|
|
if (CInfo.client_data <> nil) and
|
|
PErrorClientData(CInfo.client_data).ScanlineReadReached then
|
|
begin
|
|
// Non-local jump to error handler in TJpegFileFormat.LoadData
|
|
longjmp(PErrorClientData(CInfo.client_data).JmpBuf, 1)
|
|
end
|
|
else
|
|
RaiseError;
|
|
{$ELSE}
|
|
RaiseError;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure OutputMessage(CurInfo: j_common_ptr);
|
|
begin
|
|
end;
|
|
|
|
procedure ReleaseContext(var jc: TJpegContext);
|
|
begin
|
|
if jc.common.err = nil then
|
|
Exit;
|
|
jpeg_destroy(@jc.common);
|
|
jpeg_destroy_decompress(@jc.d);
|
|
jpeg_destroy_compress(@jc.c);
|
|
jc.common.err := nil;
|
|
end;
|
|
|
|
procedure InitSource(cinfo: j_decompress_ptr);
|
|
begin
|
|
PSourceMgr(cinfo.src).StartOfFile := True;
|
|
end;
|
|
|
|
function FillInputBuffer(cinfo: j_decompress_ptr): Boolean;
|
|
var
|
|
NBytes: LongInt;
|
|
Src: PSourceMgr;
|
|
begin
|
|
Src := PSourceMgr(cinfo.src);
|
|
NBytes := JIO.Read(Src.Input, Src.Buffer, BufferSize);
|
|
|
|
if NBytes <= 0 then
|
|
begin
|
|
PByteArray(Src.Buffer)[0] := $FF;
|
|
PByteArray(Src.Buffer)[1] := JPEG_EOI;
|
|
NBytes := 2;
|
|
end;
|
|
Src.Pub.next_input_byte := Src.Buffer;
|
|
Src.Pub.bytes_in_buffer := NBytes;
|
|
Src.StartOfFile := False;
|
|
Result := True;
|
|
end;
|
|
|
|
procedure SkipInputData(cinfo: j_decompress_ptr; num_bytes: LongInt);
|
|
var
|
|
Src: PSourceMgr;
|
|
begin
|
|
Src := PSourceMgr(cinfo.src);
|
|
if num_bytes > 0 then
|
|
begin
|
|
while num_bytes > Src.Pub.bytes_in_buffer do
|
|
begin
|
|
Dec(num_bytes, Src.Pub.bytes_in_buffer);
|
|
FillInputBuffer(cinfo);
|
|
end;
|
|
Src.Pub.next_input_byte := @PByteArray(Src.Pub.next_input_byte)[num_bytes];
|
|
//Inc(LongInt(Src.Pub.next_input_byte), num_bytes);
|
|
Dec(Src.Pub.bytes_in_buffer, num_bytes);
|
|
end;
|
|
end;
|
|
|
|
procedure TermSource(cinfo: j_decompress_ptr);
|
|
var
|
|
Src: PSourceMgr;
|
|
begin
|
|
Src := PSourceMgr(cinfo.src);
|
|
// Move stream position back just after EOI marker so that more that one
|
|
// JPEG images can be loaded from one stream
|
|
JIO.Seek(Src.Input, -Src.Pub.bytes_in_buffer, smFromCurrent);
|
|
end;
|
|
|
|
procedure JpegStdioSrc(var cinfo: jpeg_decompress_struct; Handle:
|
|
TImagingHandle);
|
|
var
|
|
Src: PSourceMgr;
|
|
begin
|
|
if cinfo.src = nil then
|
|
begin
|
|
cinfo.src := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_PERMANENT,
|
|
SizeOf(TSourceMgr));
|
|
Src := PSourceMgr(cinfo.src);
|
|
Src.Buffer := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_PERMANENT,
|
|
BufferSize * SizeOf(JOCTET));
|
|
end;
|
|
Src := PSourceMgr(cinfo.src);
|
|
Src.Pub.init_source := InitSource;
|
|
Src.Pub.fill_input_buffer := FillInputBuffer;
|
|
Src.Pub.skip_input_data := SkipInputData;
|
|
Src.Pub.resync_to_restart := jpeg_resync_to_restart;
|
|
Src.Pub.term_source := TermSource;
|
|
Src.Input := Handle;
|
|
Src.Pub.bytes_in_buffer := 0;
|
|
Src.Pub.next_input_byte := nil;
|
|
end;
|
|
|
|
procedure InitDest(cinfo: j_compress_ptr);
|
|
var
|
|
Dest: PDestMgr;
|
|
begin
|
|
Dest := PDestMgr(cinfo.dest);
|
|
Dest.Pub.next_output_byte := Dest.Buffer;
|
|
Dest.Pub.free_in_buffer := BufferSize;
|
|
end;
|
|
|
|
function EmptyOutput(cinfo: j_compress_ptr): Boolean;
|
|
var
|
|
Dest: PDestMgr;
|
|
begin
|
|
Dest := PDestMgr(cinfo.dest);
|
|
JIO.Write(Dest.Output, Dest.Buffer, BufferSize);
|
|
Dest.Pub.next_output_byte := Dest.Buffer;
|
|
Dest.Pub.free_in_buffer := BufferSize;
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TermDest(cinfo: j_compress_ptr);
|
|
var
|
|
Dest: PDestMgr;
|
|
DataCount: LongInt;
|
|
begin
|
|
Dest := PDestMgr(cinfo.dest);
|
|
DataCount := BufferSize - Dest.Pub.free_in_buffer;
|
|
if DataCount > 0 then
|
|
JIO.Write(Dest.Output, Dest.Buffer, DataCount);
|
|
end;
|
|
|
|
procedure JpegStdioDest(var cinfo: jpeg_compress_struct; Handle:
|
|
TImagingHandle);
|
|
var
|
|
Dest: PDestMgr;
|
|
begin
|
|
if cinfo.dest = nil then
|
|
cinfo.dest := cinfo.mem.alloc_small(j_common_ptr(@cinfo),
|
|
JPOOL_PERMANENT, SizeOf(TDestMgr));
|
|
Dest := PDestMgr(cinfo.dest);
|
|
Dest.Buffer := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_IMAGE,
|
|
BufferSize * SIZEOF(JOCTET));
|
|
Dest.Pub.init_destination := InitDest;
|
|
Dest.Pub.empty_output_buffer := EmptyOutput;
|
|
Dest.Pub.term_destination := TermDest;
|
|
Dest.Output := Handle;
|
|
end;
|
|
|
|
procedure SetupErrorMgr(var jc: TJpegContext);
|
|
begin
|
|
// Set standard error handlers and then override some
|
|
jc.common.err := jpeg_std_error(JpegErrorMgr);
|
|
jc.common.err.error_exit := JpegError;
|
|
jc.common.err.output_message := OutputMessage;
|
|
end;
|
|
|
|
procedure InitDecompressor(Handle: TImagingHandle; var jc: TJpegContext);
|
|
begin
|
|
jpeg_CreateDecompress(@jc.d, JPEG_LIB_VERSION, sizeof(jc.d));
|
|
JpegStdioSrc(jc.d, Handle);
|
|
jpeg_read_header(@jc.d, True);
|
|
jc.d.scale_num := 1;
|
|
jc.d.scale_denom := 1;
|
|
jc.d.do_block_smoothing := True;
|
|
if jc.d.out_color_space = JCS_GRAYSCALE then
|
|
begin
|
|
jc.d.quantize_colors := True;
|
|
jc.d.desired_number_of_colors := 256;
|
|
end;
|
|
end;
|
|
|
|
procedure InitCompressor(Handle: TImagingHandle; var jc: TJpegContext;
|
|
Saver: TJpegFileFormat);
|
|
begin
|
|
jpeg_CreateCompress(@jc.c, JPEG_LIB_VERSION, sizeof(jc.c));
|
|
JpegStdioDest(jc.c, Handle);
|
|
if Saver.FGrayScale then
|
|
jc.c.in_color_space := JCS_GRAYSCALE
|
|
else
|
|
jc.c.in_color_space := JCS_RGB;
|
|
jpeg_set_defaults(@jc.c);
|
|
jpeg_set_quality(@jc.c, Saver.FQuality, True);
|
|
if Saver.FProgressive then
|
|
jpeg_simple_progression(@jc.c);
|
|
end;
|
|
|
|
{ TJpegFileFormat class implementation }
|
|
|
|
procedure TJpegFileFormat.Define;
|
|
begin
|
|
FName := SJpegFormatName;
|
|
FFeatures := [ffLoad, ffSave];
|
|
FSupportedFormats := JpegSupportedFormats;
|
|
|
|
FQuality := JpegDefaultQuality;
|
|
FProgressive := JpegDefaultProgressive;
|
|
|
|
AddMasks(SJpegMasks);
|
|
RegisterOption(ImagingJpegQuality, @FQuality);
|
|
RegisterOption(ImagingJpegProgressive, @FProgressive);
|
|
end;
|
|
|
|
procedure TJpegFileFormat.CheckOptionsValidity;
|
|
begin
|
|
// Check if option values are valid
|
|
if not (FQuality in [1..100]) then
|
|
FQuality := JpegDefaultQuality;
|
|
end;
|
|
|
|
function TJpegFileFormat.LoadData(Handle: TImagingHandle;
|
|
var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
|
|
var
|
|
PtrInc, LinesPerCall, LinesRead, I: Integer;
|
|
Dest: PByte;
|
|
jc: TJpegContext;
|
|
Info: TImageFormatInfo;
|
|
Col32: PColor32Rec;
|
|
NeedsRedBlueSwap: Boolean;
|
|
Pix: PColor24Rec;
|
|
{$IFDEF ErrorJmpRecovery}
|
|
ErrorClient: TErrorClientData;
|
|
{$ENDIF}
|
|
|
|
procedure LoadMetaData;
|
|
var
|
|
ResUnit: TResolutionUnit;
|
|
begin
|
|
// Density unit: 0 - undef, 1 - inch, 2 - cm
|
|
if jc.d.saw_JFIF_marker and (jc.d.density_unit > 0) and
|
|
(jc.d.X_density > 0) and (jc.d.Y_density > 0) then
|
|
begin
|
|
ResUnit := ruDpi;
|
|
if jc.d.density_unit = 2 then
|
|
ResUnit := ruDpcm;
|
|
FMetadata.SetPhysicalPixelSize(ResUnit, jc.d.X_density, jc.d.Y_density);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
// Copy IO functions to global var used in JpegLib callbacks
|
|
Result := False;
|
|
SetJpegIO(GetIO);
|
|
SetLength(Images, 1);
|
|
|
|
with JIO, Images[0] do
|
|
try
|
|
ZeroMemory(@jc, SizeOf(jc));
|
|
SetupErrorMgr(jc);
|
|
{$IFDEF ErrorJmpRecovery}
|
|
ZeroMemory(@ErrorClient, SizeOf(ErrorClient));
|
|
jc.common.client_data := @ErrorClient;
|
|
if setjmp(ErrorClient.JmpBuf) <> 0 then
|
|
begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
{$ENDIF}
|
|
InitDecompressor(Handle, jc);
|
|
|
|
case jc.d.out_color_space of
|
|
JCS_GRAYSCALE: Format := ifGray8;
|
|
JCS_RGB: Format := ifR8G8B8;
|
|
JCS_CMYK: Format := ifA8R8G8B8;
|
|
else
|
|
Exit;
|
|
end;
|
|
|
|
NewImage(jc.d.image_width, jc.d.image_height, Format, Images[0]);
|
|
jpeg_start_decompress(@jc.d);
|
|
GetImageFormatInfo(Format, Info);
|
|
PtrInc := Width * Info.BytesPerPixel;
|
|
LinesPerCall := 1;
|
|
Dest := Bits;
|
|
|
|
// If Jpeg's colorspace is RGB and not YCbCr we need to swap
|
|
// R and B to get Imaging's native order
|
|
NeedsRedBlueSwap := jc.d.jpeg_color_space = JCS_RGB;
|
|
{$IFDEF RGBSWAPPED}
|
|
// Force R-B swap for FPC's PasJpeg
|
|
NeedsRedBlueSwap := True;
|
|
{$ENDIF}
|
|
|
|
{$IFDEF ErrorJmpRecovery}
|
|
ErrorClient.ScanlineReadReached := True;
|
|
{$ENDIF}
|
|
|
|
while jc.d.output_scanline < jc.d.output_height do
|
|
begin
|
|
LinesRead := jpeg_read_scanlines(@jc.d, @Dest, LinesPerCall);
|
|
if NeedsRedBlueSwap and (Format = ifR8G8B8) then
|
|
begin
|
|
Pix := PColor24Rec(Dest);
|
|
for I := 0 to Width - 1 do
|
|
begin
|
|
SwapValues(Pix.R, Pix.B);
|
|
Inc(Pix);
|
|
end;
|
|
end;
|
|
Inc(Dest, PtrInc * LinesRead);
|
|
end;
|
|
|
|
if jc.d.out_color_space = JCS_CMYK then
|
|
begin
|
|
Col32 := Bits;
|
|
// Translate from CMYK to RGB
|
|
for I := 0 to Width * Height - 1 do
|
|
begin
|
|
CMYKToRGB(255 - Col32.B, 255 - Col32.G, 255 - Col32.R, 255 - Col32.A,
|
|
Col32.R, Col32.G, Col32.B);
|
|
Col32.A := 255;
|
|
Inc(Col32);
|
|
end;
|
|
end;
|
|
|
|
// Store supported metadata
|
|
LoadMetaData;
|
|
|
|
jpeg_finish_output(@jc.d);
|
|
jpeg_finish_decompress(@jc.d);
|
|
Result := True;
|
|
finally
|
|
ReleaseContext(jc);
|
|
end;
|
|
end;
|
|
|
|
function TJpegFileFormat.SaveData(Handle: TImagingHandle;
|
|
const Images: TDynImageDataArray; Index: LongInt): Boolean;
|
|
var
|
|
PtrInc, LinesWritten: LongInt;
|
|
Src, Line: PByte;
|
|
jc: TJpegContext;
|
|
ImageToSave: TImageData;
|
|
Info: TImageFormatInfo;
|
|
MustBeFreed: Boolean;
|
|
{$IFDEF RGBSWAPPED}
|
|
I: LongInt;
|
|
Pix: PColor24Rec;
|
|
{$ENDIF}
|
|
|
|
procedure SaveMetaData;
|
|
var
|
|
XRes, YRes: Double;
|
|
begin
|
|
if FMetadata.GetPhysicalPixelSize(ruDpcm, XRes, YRes, True) then
|
|
begin
|
|
jc.c.density_unit := 2; // Dots per cm
|
|
jc.c.X_density := Round(XRes);
|
|
jc.c.Y_density := Round(YRes)
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result := False;
|
|
// Copy IO functions to global var used in JpegLib callbacks
|
|
SetJpegIO(GetIO);
|
|
|
|
// Makes image to save compatible with Jpeg saving capabilities
|
|
if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
|
|
with JIO, ImageToSave do
|
|
try
|
|
ZeroMemory(@jc, SizeOf(jc));
|
|
SetupErrorMgr(jc);
|
|
|
|
GetImageFormatInfo(Format, Info);
|
|
FGrayScale := Format = ifGray8;
|
|
InitCompressor(Handle, jc, Self);
|
|
jc.c.image_width := Width;
|
|
jc.c.image_height := Height;
|
|
if FGrayScale then
|
|
begin
|
|
jc.c.input_components := 1;
|
|
jc.c.in_color_space := JCS_GRAYSCALE;
|
|
end
|
|
else
|
|
begin
|
|
jc.c.input_components := 3;
|
|
jc.c.in_color_space := JCS_RGB;
|
|
end;
|
|
|
|
PtrInc := Width * Info.BytesPerPixel;
|
|
Src := Bits;
|
|
|
|
{$IFDEF RGBSWAPPED}
|
|
GetMem(Line, PtrInc);
|
|
{$ENDIF}
|
|
|
|
// Save supported metadata
|
|
SaveMetaData;
|
|
|
|
jpeg_start_compress(@jc.c, True);
|
|
while (jc.c.next_scanline < jc.c.image_height) do
|
|
begin
|
|
{$IFDEF RGBSWAPPED}
|
|
if Format = ifR8G8B8 then
|
|
begin
|
|
Move(Src^, Line^, PtrInc);
|
|
Pix := PColor24Rec(Line);
|
|
for I := 0 to Width - 1 do
|
|
begin
|
|
SwapValues(Pix.R, Pix.B);
|
|
Inc(Pix, 1);
|
|
end;
|
|
end;
|
|
{$ELSE}
|
|
Line := Src;
|
|
{$ENDIF}
|
|
|
|
LinesWritten := jpeg_write_scanlines(@jc.c, @Line, 1);
|
|
Inc(Src, PtrInc * LinesWritten);
|
|
end;
|
|
|
|
jpeg_finish_compress(@jc.c);
|
|
Result := True;
|
|
finally
|
|
ReleaseContext(jc);
|
|
if MustBeFreed then
|
|
FreeImage(ImageToSave);
|
|
{$IFDEF RGBSWAPPED}
|
|
FreeMem(Line);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
procedure TJpegFileFormat.ConvertToSupported(var Image: TImageData;
|
|
const Info: TImageFormatInfo);
|
|
begin
|
|
if Info.HasGrayChannel then
|
|
ConvertImage(Image, ifGray8)
|
|
else
|
|
ConvertImage(Image, ifR8G8B8);
|
|
end;
|
|
|
|
function TJpegFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
|
|
var
|
|
ReadCount: LongInt;
|
|
ID: array[0..9] of AnsiChar;
|
|
begin
|
|
Result := False;
|
|
if Handle <> nil then
|
|
with GetIO do
|
|
begin
|
|
FillChar(ID, SizeOf(ID), 0);
|
|
ReadCount := Read(Handle, @ID, SizeOf(ID));
|
|
Seek(Handle, -ReadCount, smFromCurrent);
|
|
Result := (ReadCount = SizeOf(ID)) and
|
|
CompareMem(@ID, @JpegMagic, SizeOf(JpegMagic));
|
|
end;
|
|
end;
|
|
|
|
procedure TJpegFileFormat.SetJpegIO(const JpegIO: TIOFunctions);
|
|
begin
|
|
JIO := JpegIO;
|
|
end;
|
|
|
|
initialization
|
|
RegisterImageFileFormat(TJpegFileFormat);
|
|
|
|
{
|
|
File Notes:
|
|
|
|
-- TODOS ----------------------------------------------------
|
|
- nothing now
|
|
|
|
-- 0.77.1 ---------------------------------------------------
|
|
- Able to read corrupted JPEG files - loads partial image
|
|
and skips the corrupted parts (FPC and x86 Delphi).
|
|
- Fixed reading of physical resolution metadata, could cause
|
|
"divided by zero" later on for some files.
|
|
|
|
-- 0.26.5 Changes/Bug Fixes ---------------------------------
|
|
- Fixed loading of some JPEGs with certain APPN markers (bug in JpegLib).
|
|
- Fixed swapped Red-Blue order when loading Jpegs with
|
|
jc.d.jpeg_color_space = JCS_RGB.
|
|
- Added loading and saving of physical pixel size metadata.
|
|
|
|
-- 0.26.3 Changes/Bug Fixes ---------------------------------
|
|
- Changed the Jpeg error manager, messages were not properly formatted.
|
|
|
|
-- 0.26.1 Changes/Bug Fixes ---------------------------------
|
|
- Fixed wrong color space setting in InitCompressor.
|
|
- Fixed problem with progressive Jpegs in FPC (modified JpegLib,
|
|
can't use FPC's PasJpeg in Windows).
|
|
|
|
-- 0.25.0 Changes/Bug Fixes ---------------------------------
|
|
- FPC's PasJpeg wasn't really used in last version, fixed.
|
|
|
|
-- 0.24.1 Changes/Bug Fixes ---------------------------------
|
|
- Fixed loading of CMYK jpeg images. Could cause heap corruption
|
|
and loaded image looked wrong.
|
|
|
|
-- 0.23 Changes/Bug Fixes -----------------------------------
|
|
- Removed JFIF/EXIF detection from TestFormat. Found JPEGs
|
|
with different headers (Lavc) which weren't recognized.
|
|
|
|
-- 0.21 Changes/Bug Fixes -----------------------------------
|
|
- MakeCompatible method moved to base class, put ConvertToSupported here.
|
|
GetSupportedFormats removed, it is now set in constructor.
|
|
- Made public properties for options registered to SetOption/GetOption
|
|
functions.
|
|
- Changed extensions to filename masks.
|
|
- Changed SaveData, LoadData, and MakeCompatible methods according
|
|
to changes in base class in Imaging unit.
|
|
- Changes in TestFormat, now reads JFIF and EXIF signatures too.
|
|
|
|
-- 0.19 Changes/Bug Fixes -----------------------------------
|
|
- input position is now set correctly to the end of the image
|
|
after loading is done. Loading of sequence of JPEG files stored in
|
|
single stream works now
|
|
- when loading and saving images in FPC with PASJPEG read and
|
|
blue channels are swapped to have the same chanel order as IMJPEGLIB
|
|
- you can now choose between IMJPEGLIB and PASJPEG implementations
|
|
|
|
-- 0.17 Changes/Bug Fixes -----------------------------------
|
|
- added SetJpegIO method which is used by JNG image format
|
|
}
|
|
end.
|
|
|