unit JpegObj;
{* Jpeg object. Decompression requires about 54 K (61K when err used).
Compressor part requires extra 30 Kbytes.
|
You can define conditional symbol JPEGERR in project options. In such
case exceptions will be used to handle errors, and this will increase
executable size a bit. Though, a practice shows that there are no needs
in exceptions to work correctly even with corrupted jpeg images. Moreover,
refuse from exceptions allows to show partially corrupted images, though
when exceptions are used, such images can not be decoded at all.
}
//{$DEFINE VER62} // if you plan to use .obj-files from Delphi7 distributive only!
interface
{$I KOLDEF.INC}
{$IFDEF JPEGERR}
{$IFDEF NOJPEGERR}
{$UNDEF NOJPEGERR}
{$ENDIF}
{$ENDIF}
uses windows, KOL {$IFDEF JPEGERR}, err {$ENDIF};
type
PJPEGData = ^TJPEGData;
TJPEGData = object( TObj )
private
FData: PStream;
FHeight: Integer;
FWidth: Integer;
FGrayscale: Boolean;
protected
public
destructor Destroy; virtual;
procedure Clear;
end;
TJPEGQualityRange = 1..100; // 100 = best quality, 25 = pretty awful
TJPEGPerformance = (jpBestQuality, jpBestSpeed);
TJPEGScale = (jsFullSize, jsHalf, jsQuarter, jsEighth);
TJPEGPixelFormat = (jf24Bit, jf8Bit);
PJpeg = ^TJpeg;
TJPEGProgress = procedure( Sender: PJpeg; const Rect: TRect; var Stop: Boolean )
of object;
TJpeg = object( TObj )
{* JPeg image incapsulation. If only decoding is used, about 54K of code
is attached to executable. If encoding is used too, about 30K of code
is attached additionally. }
private
FImage: PJPEGData;
FBitmap: PBitmap;
FScaledWidth: Integer;
FScaledHeight: Integer;
FTempPal: HPalette;
FSmoothing: Boolean;
FGrayScale: Boolean;
FPixelFormat: TJPEGPixelFormat;
FQuality: TJPEGQualityRange;
FProgressiveDisplay: Boolean;
FProgressiveEncoding: Boolean;
FPerformance: TJPEGPerformance;
FScale: TJPEGScale;
FNeedRecalc: Boolean;
FOnChange: TOnEvent;
FCorrupted: Boolean;
FOnProgress: TJPEGProgress;
FProgress: function( JPEGobj: PJpeg; const R: TRect ): Boolean;
FCallback: Pointer;
FStop: Boolean;
fProgressTime: Integer;
FCMYK: Boolean;
FConvertCMYK2RGBProc: procedure( Bmp: PBitmap );
FConvertCMYK2RGB: Boolean;
procedure CalcOutputDimensions;
function GetBitmap: PBitmap;
function GetGrayscale: Boolean;
procedure SetGrayscale(Value: Boolean);
procedure SetPerformance(Value: TJPEGPerformance);
procedure SetPixelFormat(Value: TJPEGPixelFormat);
procedure SetScale(Value: TJPEGScale);
procedure SetSmoothing(Value: Boolean);
procedure SetOnProgress(const Value: TJPEGProgress);
procedure SetBitmap(const Value: PBitmap);
procedure SetConvertCMYK2RGB(const Value: Boolean);
protected
function GetEmpty: Boolean;
{*}
procedure FreeBitmap;
{* Call it to free bitmap, containing decoded JPeg image. }
function GetHeight: Integer;
{*}
function GetWidth: Integer;
{*}
function GetPalette: HPALETTE;
{* }
procedure Changed;
// internal methods. Do not know why not placed into 'private' section.
procedure CreateBitmap;
procedure CreateImage;
procedure ReadData(Stream: PStream);
procedure ReadStream(Size: Integer; Stream: PStream);
procedure SetHeight(Value: Integer);
procedure SetPalette(Value: HPalette);
procedure SetWidth(Value: Integer);
procedure WriteData(Stream: PStream);
public
destructor Destroy; virtual;
{*}
procedure Clear; virtual;
{*}
procedure Compress;
{* }
procedure DIBNeeded;
{* }
procedure JPEGNeeded;
{* }
procedure Draw(DC : HDC; X, Y : Integer);
{*}
procedure StretchDraw( DC : HDC; Dest : TRect );
{*}
property Palette : HPalette read GetPalette write SetPalette;
{* }
procedure LoadFromStream(Stream: PStream);
{* Loads JPeg image from a stream (from current position). }
procedure SaveToStream(Stream: PStream);
{* Saves JPeg image to a stream. }
function LoadFromFile( const FName : String ) : Boolean;
{* Function to load jpeg image from a file. }
function SaveToFile( const FName : String ) : Boolean;
{* Function to save jpeg image into a file. }
// 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;
{* Compression quality. }
// Decompression options
property PixelFormat: TJPEGPixelFormat read FPixelFormat write SetPixelFormat;
{* }
{* Format of decompressed bitmap. }
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;
{* True, if smoothing is enabled due decompression. }
property Bitmap: PBitmap read GetBitmap write SetBitmap;
{* Returns decompressed jpeg image as a bitmap. To detect if an image
is corrupted, check Corrupted property after requesting the Bitmap.
|
Assign a TBitmap object to this property before calling method
SaveToStream or SaveToFile to convert bitmap image to jpeg format. }
property Empty: Boolean read GetEmpty;
{* Returns True, if empty. }
property OnChange: TOnEvent read FOnChange write FOnChange;
{* Is called when the image is changed. }
property Width: Integer read GetWidth;
{* }
property Height: Integer read GetHeight;
{* }
property Corrupted: Boolean read FCorrupted;
{* True, when an image is corrupted. This can be detected only AFTER
Bitmap is requested, not just after loading the image. }
property OnProgress: TJPEGProgress read FOnProgress write SetOnProgress;
{* This event is called while decompressing (or compressing) the image.
If You want paint portion of decompressed image, use Bitmap method
DIBDrawRect, which does not change its DIBBits location during decoding.
Otherwise, the abnormal termination can be caused. }
property ProgressTime: Integer read fProgressTime write fProgressTime;
{* By default, 100 milliseconds. Change this period to change frequency
of OnProgress event calls during the compression / decompression. }
property ConvertCMYK2RGB: Boolean read FConvertCMYK2RGB write SetConvertCMYK2RGB;
{* Set it to true to convert decoded CMYK image to RGB. }
end;
TJPEGDefaults = record
CompressionQuality: TJPEGQualityRange;
Grayscale: Boolean;
Performance: TJPEGPerformance;
PixelFormat: TJPEGPixelFormat;
ProgressiveDisplay: Boolean;
ProgressiveEncoding: Boolean;
Scale: TJPEGScale;
Smoothing: Boolean;
end;
function NewJpeg: PJpeg;
{* Constructs new TJpeg object. }
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
{ 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. }
{$Z4} // Minimum enum size = dword
const
JPEG_LIB_VERSION = {$IFDEF VER62} 62 {$ELSE} 61 {$ENDIF}; { Version 6a }
JPEG_RST0 = $D0; { RST0 marker code }
JPEG_EOI = $D9; { EOI marker code }
JPEG_APP0 = $E0; { APP0 marker code }
JPEG_COM = $FE; { COM marker code }
DCTSIZE = 8; { The basic DCT block is 8x8 samples }
DCTSIZE2 = 64; { DCTSIZE squared; # of elements in a block }
NUM_QUANT_TBLS = 4; { Quantization tables are numbered 0..3 }
NUM_HUFF_TBLS = 4; { Huffman tables are numbered 0..3 }
NUM_ARITH_TBLS = 16; { Arith-coding tables are numbered 0..15 }
MAX_COMPS_IN_SCAN = 4; { JPEG limit on # of components in one scan }
MAX_SAMP_FACTOR = 4; { JPEG limit on sampling factors }
C_MAX_BLOCKS_IN_MCU = 10; { compressor's limit on blocks per MCU }
D_MAX_BLOCKS_IN_MCU = 10; { decompressor's limit on blocks per MCU }
MAX_COMPONENTS = 10; { maximum number of image components (color channels) }
MAXJSAMPLE = 255;
CENTERJSAMPLE = 128;
type
JSAMPLE = byte;
GETJSAMPLE = integer;
JCOEF = integer;
JCOEF_PTR = ^JCOEF;
UINT8 = byte;
UINT16 = Word;
UINT = Cardinal;
INT16 = SmallInt;
INT32 = Integer;
INT32PTR = ^INT32;
JDIMENSION = Cardinal;
JOCTET = Byte;
jTOctet = 0..(MaxInt div SizeOf(JOCTET))-1;
JOCTET_FIELD = array[jTOctet] of JOCTET;
JOCTET_FIELD_PTR = ^JOCTET_FIELD;
JOCTETPTR = ^JOCTET;
JSAMPLE_PTR = ^JSAMPLE;
JSAMPROW_PTR = ^JSAMPROW;
jTSample = 0..(MaxInt div SIZEOF(JSAMPLE))-1;
JSAMPLE_ARRAY = Array[jTSample] of JSAMPLE; {far}
JSAMPROW = ^JSAMPLE_ARRAY; { ptr to one image row of pixel samples. }
jTRow = 0..(MaxInt div SIZEOF(JSAMPROW))-1;
JSAMPROW_ARRAY = Array[jTRow] of JSAMPROW;
JSAMPARRAY = ^JSAMPROW_ARRAY; { ptr to some rows (a 2-D sample array) }
jTArray = 0..(MaxInt div SIZEOF(JSAMPARRAY))-1;
JSAMP_ARRAY = Array[jTArray] of JSAMPARRAY;
JSAMPIMAGE = ^JSAMP_ARRAY; { a 3-D sample array: top index is color }
const
CSTATE_START = 100; { after create_compress }
CSTATE_SCANNING = 101; { start_compress done, write_scanlines OK }
CSTATE_RAW_OK = 102; { start_compress done, write_raw_data OK }
CSTATE_WRCOEFS = 103; { jpeg_write_coefficients done }
DSTATE_START = 200; { after create_decompress }
DSTATE_INHEADER = 201; { reading header markers, no SOS yet }
DSTATE_READY = 202; { found SOS, ready for start_decompress }
DSTATE_PRELOAD = 203; { reading multiscan file in start_decompress}
DSTATE_PRESCAN = 204; { performing dummy pass for 2-pass quant }
DSTATE_SCANNING = 205; { start_decompress done, read_scanlines OK }
DSTATE_RAW_OK = 206; { start_decompress done, read_raw_data OK }
DSTATE_BUFIMAGE = 207; { expecting jpeg_start_output }
DSTATE_BUFPOST = 208; { looking for SOS/EOI in jpeg_finish_output }
DSTATE_RDCOEFS = 209; { reading file in jpeg_read_coefficients }
DSTATE_STOPPING = 210; { looking for EOI in jpeg_finish_decompress }
{ Known color spaces. }
type
J_COLOR_SPACE = (
JCS_UNKNOWN, { error/unspecified }
JCS_GRAYSCALE, { monochrome }
JCS_RGB, { red/green/blue }
JCS_YCbCr, { Y/Cb/Cr (also known as YUV) }
JCS_CMYK, { C/M/Y/K }
JCS_YCCK { Y/Cb/Cr/K }
);
{ DCT/IDCT algorithm options. }
type
J_DCT_METHOD = (
JDCT_ISLOW, { slow but accurate integer algorithm }
JDCT_IFAST, { faster, less accurate integer method }
JDCT_FLOAT { floating-point: accurate, fast on fast HW (Pentium)}
);
{ Dithering options for decompression. }
type
J_DITHER_MODE = (
JDITHER_NONE, { no dithering }
JDITHER_ORDERED, { simple ordered dither }
JDITHER_FS { Floyd-Steinberg error diffusion dither }
);
{ Error handler }
const
JMSG_LENGTH_MAX = 200; { recommended size of format_message buffer }
JMSG_STR_PARM_MAX = 80;
JPOOL_PERMANENT = 0; // lasts until master record is destroyed
JPOOL_IMAGE = 1; // lasts until done with image/datastream
type
jpeg_error_mgr_ptr = ^jpeg_error_mgr;
jpeg_progress_mgr_ptr = ^jpeg_progress_mgr;
j_common_ptr = ^jpeg_common_struct;
j_compress_ptr = ^jpeg_compress_struct;
j_decompress_ptr = ^jpeg_decompress_struct;
{ Routine signature for application-supplied marker processing methods.
Need not pass marker code since it is stored in cinfo^.unread_marker. }
jpeg_marker_parser_method = function(cinfo : j_decompress_ptr) : LongBool;
jpeg_saved_marker_ptr = ^jpeg_marker_struct;
jpeg_marker_struct = record
next: jpeg_saved_marker_ptr; { next in list, or NULL }
marker: Byte; { marker code: JPEG_COM, or JPEG_APP0+n }
original_length: LongWord; { # bytes of data in the file }
data_length: LongWord; { # bytes of data saved at data[] }
data: JOCTETPTR; { the data contained in the marker }
{ the marker length word is not counted in data_length or original_length }
end;
{ Marker reading & parsing }
jpeg_marker_reader_ptr = ^jpeg_marker_reader;
jpeg_marker_reader = record
reset_marker_reader : procedure(cinfo : j_decompress_ptr);
{ Read markers until SOS or EOI.
Returns same codes as are defined for jpeg_consume_input:
JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. }
read_markers : function (cinfo : j_decompress_ptr) : Integer;
{ Read a restart marker --- exported for use by entropy decoder only }
read_restart_marker : jpeg_marker_parser_method;
{ Application-overridable marker processing methods }
process_COM : jpeg_marker_parser_method;
process_APPn : Array[0..16-1] of jpeg_marker_parser_method;
{ State of marker reader --- nominally internal, but applications
supplying COM or APPn handlers might like to know the state. }
saw_SOI : LongBool; { found SOI? }
saw_SOF : LongBool; { found SOF? }
next_restart_num : Integer; { next restart number expected (0-7) }
discarded_bytes : UINT; { # of bytes skipped looking for a marker }
end;
{int8array = Array[0..8-1] of int;}
int8array = Array[0..8-1] of Integer;
jpeg_error_mgr = record
{ Error exit handler: does not return to caller }
error_exit : procedure (cinfo : j_common_ptr);
{ Conditionally emit a trace or warning message }
emit_message : procedure (cinfo : j_common_ptr; msg_level : Integer);
{ Routine that actually outputs a trace or error message }
output_message : procedure (cinfo : j_common_ptr);
{ Format a message string for the most recent JPEG error or message }
format_message : procedure (cinfo : j_common_ptr; buffer: PChar);
{ Reset error state variables at start of a new image }
reset_error_mgr : procedure (cinfo : j_common_ptr);
{ The message ID code and any parameters are saved here.
A message can have one string parameter or up to 8 int parameters. }
msg_code : Integer;
msg_parm : record
case byte of
0:(i : int8array);
{$IFDEF VER62}
1:(s : array[0..JMSG_STR_PARM_MAX - 1] of char);
{$ELSE}
1:(s : string[JMSG_STR_PARM_MAX]);
{$ENDIF}
end;
trace_level : Integer; { max msg_level that will be displayed }
num_warnings : Integer; { number of corrupt-data warnings }
{$IFDEF VER62}
jpeg_message_table: ^PChar; { Library errors }
last_jpeg_message: Integer; { Table contains strings 0..last_jpeg_message }
{ Second table can be added by application (see cjpeg/djpeg for example).
It contains strings numbered first_addon_message..last_addon_message.
}
addon_message_table: ^PChar; { Non-library errors }
first_addon_message: Integer; { code for first string in addon table }
last_addon_message: Integer; { code for last string in addon table }
{$ENDIF}
end;
{ Data destination object for compression }
jpeg_destination_mgr_ptr = ^jpeg_destination_mgr;
jpeg_destination_mgr = record
next_output_byte : JOCTETptr; { => next byte to write in buffer }
free_in_buffer : Longint; { # of byte spaces remaining in buffer }
init_destination : procedure (cinfo : j_compress_ptr);
empty_output_buffer : function (cinfo : j_compress_ptr) : LongBool;
term_destination : procedure (cinfo : j_compress_ptr);
end;
{ Data source object for decompression }
jpeg_source_mgr_ptr = ^jpeg_source_mgr;
jpeg_source_mgr = record
next_input_byte : JOCTETptr; { => next byte to read from buffer }
bytes_in_buffer : Longint; { # of bytes remaining in buffer }
init_source : procedure (cinfo : j_decompress_ptr);
fill_input_buffer : function (cinfo : j_decompress_ptr) : LongBool;
skip_input_data : procedure (cinfo : j_decompress_ptr; num_bytes : Longint);
resync_to_restart : function (cinfo : j_decompress_ptr;
desired : Integer) : LongBool;
term_source : procedure (cinfo : j_decompress_ptr);
end;
{ JPEG library memory manger routines }
jpeg_memory_mgr_ptr = ^jpeg_memory_mgr;
jpeg_memory_mgr = record
{ Method pointers }
alloc_small : function (cinfo : j_common_ptr;
pool_id, sizeofobject: Integer): pointer;
alloc_large : function (cinfo : j_common_ptr;
pool_id, sizeofobject: Integer): pointer;
alloc_sarray : function (cinfo : j_common_ptr; pool_id : Integer;
samplesperrow : JDIMENSION;
numrows : JDIMENSION) : JSAMPARRAY;
alloc_barray : pointer;
request_virt_sarray : pointer;
request_virt_barray : pointer;
realize_virt_arrays : pointer;
access_virt_sarray : pointer;
access_virt_barray : pointer;
free_pool : pointer;
self_destruct : pointer;
max_memory_to_use : Longint;
end;
{ Fields shared with jpeg_decompress_struct }
jpeg_common_struct = packed record
err : jpeg_error_mgr_ptr; { Error handler module }
mem : jpeg_memory_mgr_ptr; { Memory manager module }
progress : jpeg_progress_mgr_ptr; { Progress monitor, or NIL if none }
{$IFDEF VER62}
client_data: Pointer; { Available for use by application }
{$ENDIF}
is_decompressor : LongBool; { so common code can tell which is which }
global_state : Integer; { for checking call sequence validity }
end;
{ Progress monitor object }
jpeg_progress_mgr = record
progress_monitor : procedure(const cinfo : jpeg_common_struct);
pass_counter : Integer; { work units completed in this pass }
pass_limit : Integer; { total number of work units in this pass }
completed_passes : Integer; { passes completed so far }
total_passes : Integer; { total number of passes expected }
// extra Delphi info
instance: PJpeg; // ptr to current PJpeg object
last_pass: Integer;
last_pct: Integer;
last_time: Integer;
last_scanline: Integer;
end;
{ Master record for a compression instance }
jpeg_compress_struct = {$IFNDEF VER62} packed {$ENDIF} record
common: jpeg_common_struct;
dest : jpeg_destination_mgr_ptr; { Destination for compressed data }
{ Description of source image --- these fields must be filled in by
outer application before starting compression. in_color_space must
be correct before you can even call jpeg_set_defaults(). }
image_width : JDIMENSION; { input image width }
image_height : JDIMENSION; { input image height }
input_components : Integer; { # of color components in input image }
in_color_space : J_COLOR_SPACE; { colorspace of input image }
input_gamma : double; { image gamma of input image }
// Compression parameters
data_precision : Integer; { bits of precision in image data }
num_components : Integer; { # of color components in JPEG image }
jpeg_color_space : J_COLOR_SPACE; { colorspace of JPEG image }
comp_info : Pointer;
quant_tbl_ptrs: Array[0..NUM_QUANT_TBLS-1] of Pointer;
dc_huff_tbl_ptrs : Array[0..NUM_HUFF_TBLS-1] of Pointer;
ac_huff_tbl_ptrs : Array[0..NUM_HUFF_TBLS-1] of Pointer;
arith_dc_L : Array[0..NUM_ARITH_TBLS-1] of UINT8; { L values for DC arith-coding tables }
arith_dc_U : Array[0..NUM_ARITH_TBLS-1] of UINT8; { U values for DC arith-coding tables }
arith_ac_K : Array[0..NUM_ARITH_TBLS-1] of UINT8; { Kx values for AC arith-coding tables }
num_scans : Integer; { # of entries in scan_info array }
scan_info : Pointer; { script for multi-scan file, or NIL }
raw_data_in : LongBool; { TRUE=caller supplies downsampled data }
arith_code : LongBool; { TRUE=arithmetic coding, FALSE=Huffman }
optimize_coding : LongBool; { TRUE=optimize entropy encoding parms }
CCIR601_sampling : LongBool; { TRUE=first samples are cosited }
smoothing_factor : Integer; { 1..100, or 0 for no input smoothing }
dct_method : J_DCT_METHOD; { DCT algorithm selector }
restart_interval : UINT; { MCUs per restart, or 0 for no restart }
restart_in_rows : Integer; { if > 0, MCU rows per restart interval }
{ Parameters controlling emission of special markers. }
write_JFIF_header : LongBool; { should a JFIF marker be written? }
{$IFDEF VER62}
JFIF_major_version: UINT8; { What to write for a JFIF version number }
JFIF_minor_version: UINT8;
{$ENDIF}
{ These three values are not used by the JPEG code, merely copied }
{ into the JFIF APP0 marker. density_unit can be 0 for unknown, }
{ 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect }
{ ratio is defined by X_density/Y_density even when density_unit=0. }
density_unit : UINT8; { JFIF code for pixel size units }
X_density : UINT16; { Horizontal pixel density }
Y_density : UINT16; { Vertical pixel density }
write_Adobe_marker : LongBool; { should an Adobe marker be written? }
{ State variable: index of next scanline to be written to
jpeg_write_scanlines(). Application may use this to control its
processing loop, e.g., "while (next_scanline < image_height)". }
next_scanline : JDIMENSION; { 0 .. image_height-1 }
{ Remaining fields are known throughout compressor, but generally
should not be touched by a surrounding application. }
progressive_mode : LongBool; { TRUE if scan script uses progressive mode }
max_h_samp_factor : Integer; { largest h_samp_factor }
max_v_samp_factor : Integer; { largest v_samp_factor }
total_iMCU_rows : JDIMENSION; { # of iMCU rows to be input to coef ctlr }
comps_in_scan : Integer; { # of JPEG components in this scan }
cur_comp_info : Array[0..MAX_COMPS_IN_SCAN-1] of Pointer;
MCUs_per_row : JDIMENSION; { # of MCUs across the image }
MCU_rows_in_scan : JDIMENSION;{ # of MCU rows in the image }
blocks_in_MCU : Integer; { # of DCT blocks per MCU }
MCU_membership : Array[0..C_MAX_BLOCKS_IN_MCU-1] of Integer;
Ss, Se, Ah, Al : Integer; { progressive JPEG parameters for scan }
{ Links to compression subobjects (methods and private variables of modules) }
master : Pointer;
main : Pointer;
prep : Pointer;
coef : Pointer;
marker : Pointer;
cconvert : Pointer;
downsample : Pointer;
fdct : Pointer;
entropy : Pointer;
{$IFDEF VER62}
script_space: Pointer; { workspace for jpeg_simple_progression }
script_space_size: Integer;
{$ENDIF}
end;
{ Master record for a decompression instance }
jpeg_decompress_struct = {$IFNDEF VER62} packed {$ENDIF} record
common: jpeg_common_struct;
{ Source of compressed data }
src : jpeg_source_mgr_ptr;
{ Basic description of image --- filled in by jpeg_read_header(). }
{ Application may inspect these values to decide how to process image. }
image_width : JDIMENSION; { nominal image width (from SOF marker) }
image_height : JDIMENSION; { nominal image height }
num_components : Integer; { # of color components in JPEG image }
jpeg_color_space : J_COLOR_SPACE; { colorspace of JPEG image }
{ Decompression processing parameters }
out_color_space : J_COLOR_SPACE; { colorspace for output }
scale_num, scale_denom : uint ; { fraction by which to scale image }
output_gamma : double; { image gamma wanted in output }
buffered_image : LongBool; { TRUE=multiple output passes }
raw_data_out : LongBool; { TRUE=downsampled data wanted }
dct_method : J_DCT_METHOD; { IDCT algorithm selector }
do_fancy_upsampling : LongBool; { TRUE=apply fancy upsampling }
do_block_smoothing : LongBool; { TRUE=apply interblock smoothing }
quantize_colors : LongBool; { TRUE=colormapped output wanted }
{ the following are ignored if not quantize_colors: }
dither_mode : J_DITHER_MODE; { type of color dithering to use }
two_pass_quantize : LongBool; { TRUE=use two-pass color quantization }
desired_number_of_colors : Integer; { max # colors to use in created colormap }
{ these are significant only in buffered-image mode: }
enable_1pass_quant : LongBool; { enable future use of 1-pass quantizer }
enable_external_quant : LongBool; { enable future use of external colormap }
enable_2pass_quant : LongBool; { enable future use of 2-pass quantizer }
{ Description of actual output image that will be returned to application.
These fields are computed by jpeg_start_decompress().
You can also use jpeg_calc_output_dimensions() to determine these values
in advance of calling jpeg_start_decompress(). }
output_width : JDIMENSION; { scaled image width }
output_height: JDIMENSION; { scaled image height }
out_color_components : Integer; { # of color components in out_color_space }
output_components : Integer; { # of color components returned }
{ output_components is 1 (a colormap index) when quantizing colors;
otherwise it equals out_color_components. }
rec_outbuf_height : Integer; { min recommended height of scanline buffer }
{ If the buffer passed to jpeg_read_scanlines() is less than this many
rows high, space and time will be wasted due to unnecessary data
copying. Usually rec_outbuf_height will be 1 or 2, at most 4. }
{ When quantizing colors, the output colormap is described by these
fields. The application can supply a colormap by setting colormap
non-NIL before calling jpeg_start_decompress; otherwise a colormap
is created during jpeg_start_decompress or jpeg_start_output. The map
has out_color_components rows and actual_number_of_colors columns. }
actual_number_of_colors : Integer; { number of entries in use }
colormap : JSAMPARRAY; { The color map as a 2-D pixel array }
{ State variables: these variables indicate the progress of decompression.
The application may examine these but must not modify them. }
{ Row index of next scanline to be read from jpeg_read_scanlines().
Application may use this to control its processing loop, e.g.,
"while (output_scanline < output_height)". }
output_scanline : JDIMENSION; { 0 .. output_height-1 }
{ Current input scan number and number of iMCU rows completed in scan.
These indicate the progress of the decompressor input side. }
input_scan_number : Integer; { Number of SOS markers seen so far }
input_iMCU_row : JDIMENSION; { Number of iMCU rows completed }
{ The "output scan number" is the notional scan being displayed by the
output side. The decompressor will not allow output scan/row number
to get ahead of input scan/row, but it can fall arbitrarily far behind.}
output_scan_number : Integer; { Nominal scan number being displayed }
output_iMCU_row : Integer; { Number of iMCU rows read }
coef_bits : Pointer;
{ Internal JPEG parameters --- the application usually need not look at
these fields. Note that the decompressor output side may not use
any parameters that can change between scans. }
{ Quantization and Huffman tables are carried forward across input
datastreams when processing abbreviated JPEG datastreams. }
quant_tbl_ptrs : Array[0..NUM_QUANT_TBLS-1] of Pointer;
dc_huff_tbl_ptrs : Array[0..NUM_HUFF_TBLS-1] of Pointer;
ac_huff_tbl_ptrs : Array[0..NUM_HUFF_TBLS-1] of Pointer;
{ These parameters are never carried across datastreams, since they
are given in SOF/SOS markers or defined to be reset by SOI. }
data_precision : Integer; { bits of precision in image data }
comp_info : Pointer;
progressive_mode : LongBool; { TRUE if SOFn specifies progressive mode }
arith_code : LongBool; { TRUE=arithmetic coding, FALSE=Huffman }
arith_dc_L : Array[0..NUM_ARITH_TBLS-1] of UINT8; { L values for DC arith-coding tables }
arith_dc_U : Array[0..NUM_ARITH_TBLS-1] of UINT8; { U values for DC arith-coding tables }
arith_ac_K : Array[0..NUM_ARITH_TBLS-1] of UINT8; { Kx values for AC arith-coding tables }
restart_interval : UINT; { MCUs per restart interval, or 0 for no restart }
{ These fields record data obtained from optional markers recognized by
the JPEG library. }
saw_JFIF_marker : LongBool; { TRUE iff a JFIF APP0 marker was found }
{ Data copied from JFIF marker: }
{$IFDEF VER62}
JFIF_major_version: UINT8; { JFIF version number }
JFIF_minor_version: UINT8;
{$ENDIF}
density_unit : UINT8; { JFIF code for pixel size units }
X_density : UINT16; { Horizontal pixel density }
Y_density : UINT16; { Vertical pixel density }
saw_Adobe_marker : LongBool; { TRUE iff an Adobe APP14 marker was found }
Adobe_transform : UINT8; { Color transform code from Adobe marker }
CCIR601_sampling : LongBool; { TRUE=first samples are cosited }
{$IFDEF VER62}
{ Aside from the specific data retained from APPn markers known to the
library, the uninterpreted contents of any or all APPn and COM markers
can be saved in a list for examination by the application. }
marker_list: jpeg_saved_marker_ptr; { Head of list of saved markers }
{$ENDIF}
{ Remaining fields are known throughout decompressor, but generally
should not be touched by a surrounding application. }
max_h_samp_factor : Integer; { largest h_samp_factor }
max_v_samp_factor : Integer; { largest v_samp_factor }
min_DCT_scaled_size : Integer; { smallest DCT_scaled_size of any component }
total_iMCU_rows : JDIMENSION; { # of iMCU rows in image }
sample_range_limit : Pointer; { table for fast range-limiting }
{ These fields are valid during any one scan.
They describe the components and MCUs actually appearing in the scan.
Note that the decompressor output side must not use these fields. }
comps_in_scan : Integer; { # of JPEG components in this scan }
cur_comp_info : Array[0..MAX_COMPS_IN_SCAN-1] of Pointer;
MCUs_per_row : JDIMENSION; { # of MCUs across the image }
MCU_rows_in_scan : JDIMENSION; { # of MCU rows in the image }
blocks_in_MCU : JDIMENSION; { # of DCT blocks per MCU }
MCU_membership : Array[0..D_MAX_BLOCKS_IN_MCU-1] of Integer;
Ss, Se, Ah, Al : Integer; { progressive JPEG parameters for scan }
{ This field is shared between entropy decoder and marker parser.
It is either zero or the code of a JPEG marker that has been
read from the data source, but has not yet been processed. }
unread_marker : Integer;
{ Links to decompression subobjects
(methods, private variables of modules) }
master : Pointer;
main : Pointer;
coef : Pointer;
post : Pointer;
inputctl : Pointer;
marker : Pointer;
entropy : Pointer;
idct : Pointer;
upsample : Pointer;
cconvert : Pointer;
cquantize : Pointer;
end;
TJPEGContext = record
err: jpeg_error_mgr;
progress: jpeg_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;
{ Decompression startup: read start of JPEG datastream to see what's there
function jpeg_read_header (cinfo : j_decompress_ptr;
require_image : LongBool) : Integer;
Return value is one of: }
const
JPEG_SUSPENDED = 0; { Suspended due to lack of input data }
JPEG_HEADER_OK = 1; { Found valid image datastream }
JPEG_HEADER_TABLES_ONLY = 2; { Found valid table-specs-only datastream }
{ If you pass require_image = TRUE (normal case), you need not check for
a TABLES_ONLY return code; an abbreviated file will cause an error exit.
JPEG_SUSPENDED is only possible if you use a data source module that can
give a suspension return (the stdio source module doesn't). }
{ function jpeg_consume_input (cinfo : j_decompress_ptr) : Integer;
Return value is one of: }
JPEG_REACHED_SOS = 1; { Reached start of new scan }
JPEG_REACHED_EOI = 2; { Reached end of image }
JPEG_ROW_COMPLETED = 3; { Completed one iMCU row }
JPEG_SCAN_COMPLETED = 4; { Completed last iMCU row of a scan }
// Stubs for external C RTL functions referenced by JPEG OBJ files.
function _malloc(size: Integer): Pointer; cdecl;
begin
GetMem(Result, size);
end;
procedure _free(P: Pointer); cdecl;
begin
FreeMem(P);
end;
procedure _memset(P: Pointer; B: Byte; count: Integer); cdecl;
begin
FillChar(P^, count, B);
end;
procedure _memcpy(dest, source: Pointer; count: Integer); cdecl;
begin
Move(source^, dest^, count);
end;
function _fread(var buf; recsize, reccount: Integer; S: PStream): Integer; cdecl;
begin
Result := S.Read(buf, recsize * reccount);
end;
function _fwrite(var buf; recsize, reccount: Integer; S: PStream): Integer; cdecl;
begin
Result := S.Write(buf, recsize * reccount);
end;
function _fflush(S: PStream): Integer; cdecl;
begin
Result := 0;
end;
function __ftol: Integer;
var
f: double;
begin
asm
lea eax, f // BC++ passes floats on the FPU stack
fstp qword ptr [eax] // Delphi passes floats on the CPU stack
end;
Result := Integer(Trunc(f));
end;
var
__turboFloat: LongBool = False;
{$L JPegObj\jdapimin.obj}
{$L JPegObj\jmemmgr.obj}
{$L JPegObj\jmemnobs.obj}
{$L JPegObj\jdinput.obj}
{$L JPegObj\jdatasrc.obj}
{$L JPegObj\jdapistd.obj}
{$L JPegObj\jdmaster.obj}
{$L JPegObj\jdphuff.obj}
{$L JPegObj\jdhuff.obj}
{$L JPegObj\jdmerge.obj}
{$L JPegObj\jdcolor.obj}
{$L JPegObj\jquant1.obj}
{$L JPegObj\jquant2.obj}
{$L JPegObj\jdmainct.obj}
{$L JPegObj\jdcoefct.obj}
{$L JPegObj\jdpostct.obj}
{$L JPegObj\jddctmgr.obj}
{$L JPegObj\jdsample.obj}
{$L JPegObj\jidctflt.obj}
{$L JPegObj\jidctfst.obj}
{$L JPegObj\jidctint.obj}
{$L JPegObj\jidctred.obj}
{$L JPegObj\jdmarker.obj}
{$L JPegObj\jutils.obj}
{$L JPegObj\jcomapi.obj}
procedure jpeg_CreateDecompress (var cinfo : jpeg_decompress_struct;
version : integer; structsize : integer); external;
procedure jpeg_stdio_src(var cinfo: jpeg_decompress_struct;
input_file: PStream); external;
procedure jpeg_read_header(var cinfo: jpeg_decompress_struct;
RequireImage: LongBool); external;
procedure jpeg_calc_output_dimensions(var cinfo: jpeg_decompress_struct); external;
function jpeg_start_decompress(var cinfo: jpeg_decompress_struct): Longbool; external;
function jpeg_read_scanlines(var cinfo: jpeg_decompress_struct;
scanlines: JSAMPARRAY; max_lines: JDIMENSION): JDIMENSION; external;
function jpeg_finish_decompress(var cinfo: jpeg_decompress_struct): Longbool; external;
procedure jpeg_destroy_decompress (var cinfo : jpeg_decompress_struct); external;
function jpeg_has_multiple_scans(var cinfo: jpeg_decompress_struct): Longbool; external;
function jpeg_consume_input(var cinfo: jpeg_decompress_struct): Integer; external;
function jpeg_start_output(var cinfo: jpeg_decompress_struct; scan_number: Integer): Longbool; external;
function jpeg_finish_output(var cinfo: jpeg_decompress_struct): LongBool; external;
procedure jpeg_destroy(var cinfo: jpeg_common_struct); external;
{$L JPegObj\jdatadst.obj}
{$L JPegObj\jcparam.obj}
{$L JPegObj\jcapistd.obj}
{$L JPegObj\jcapimin.obj}
{$L JPegObj\jcinit.obj}
{$L JPegObj\jcmarker.obj}
{$L JPegObj\jcmaster.obj}
{$L JPegObj\jcmainct.obj}
{$L JPegObj\jcprepct.obj}
{$L JPegObj\jccoefct.obj}
{$L JPegObj\jccolor.obj}
{$L JPegObj\jcsample.obj}
{$L JPegObj\jcdctmgr.obj}
{$L JPegObj\jcphuff.obj}
{$L JPegObj\jfdctint.obj}
{$L JPegObj\jfdctfst.obj}
{$L JPegObj\jfdctflt.obj}
{$L JPegObj\jchuff.obj}
procedure jpeg_CreateCompress (var cinfo : jpeg_compress_struct;
version : integer; structsize : integer); external;
procedure jpeg_stdio_dest(var cinfo: jpeg_compress_struct;
output_file: PStream); external;
procedure jpeg_set_defaults(var cinfo: jpeg_compress_struct); external;
procedure jpeg_set_quality(var cinfo: jpeg_compress_struct; Quality: Integer;
Baseline: Longbool); external;
procedure jpeg_set_colorspace(var cinfo: jpeg_compress_struct;
colorspace: J_COLOR_SPACE); external;
procedure jpeg_simple_progression(var cinfo: jpeg_compress_struct); external;
procedure jpeg_start_compress(var cinfo: jpeg_compress_struct;
WriteAllTables: LongBool); external;
function jpeg_write_scanlines(var cinfo: jpeg_compress_struct;
scanlines: JSAMPARRAY; max_lines: JDIMENSION): JDIMENSION; external;
procedure jpeg_finish_compress(var cinfo: jpeg_compress_struct); external;
var Jpeg_Error: Boolean = FALSE;
procedure InvalidOperation(const Msg: string); near;
begin
//raise EInvalidGraphicOperation.Create(Msg);
//MessageBox( 0, PChar(Msg), 'JPeg message: Invalid Operation', MB_OK );
{$IFDEF JPEGERR}
raise Exception.Create( e_Convert, 'Jpeg: InvalidOp' );
{$ELSE}
Jpeg_Error := TRUE;
{$ENDIF}
end;
procedure JpegError(cinfo: j_common_ptr);
begin
//TODO: raise EJPEG.CreateFmt(sJPEGError,[cinfo^.err^.msg_code]);
{MessageBox( 0, PChar( 'JPeg error ' + #13 +
'err: ' + Int2Str( Integer( cinfo.err ) ) + #13 +
'mem: ' + Int2Str( Integer( cinfo.mem ) ) + #13 +
'progress: ' + Int2Str( Integer( cinfo.progress ) ) + #13 +
'is_decompressor: ' + Int2Str( Integer( cinfo.is_decompressor ) ) + #13 +
'global_state: ' + Int2Str( cinfo.global_state ) ),
'JPeg error', MB_OK );}
{$IFDEF JPEGERR}
raise Exception.CreateFmt( e_Convert,
'Jpeg error: %d, mem: %d, progress: %d, isDecompressor: %d, globalState: %d',
[ cinfo.err, cinfo.mem, cinfo.progress, cinfo.is_decompressor, cinfo.global_state ] );
{$ELSE}
Jpeg_Error := TRUE;
{$ENDIF}
end;
procedure EmitMessage(cinfo: j_common_ptr; msg_level: Integer);
begin
//!!
end;
procedure OutputMessage(cinfo: j_common_ptr);
begin
//!!
end;
procedure FormatMessage(cinfo: j_common_ptr; buffer: PChar);
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 }
procedure TJPEGData.Clear;
begin
if FData <> nil then
FData.Size := 0;
FWidth := 0;
FHeight := 0;
end;
destructor TJPEGData.Destroy;
begin
FData.Free;
inherited;
end;
procedure DummyProgressCallback( const cinfo: jpeg_common_struct );
begin
// * nothing *
end;
procedure ProgressCallback(const cinfo: jpeg_common_struct);
var
Ticks: Integer;
R: TRect;
temp: Integer;
begin
if (cinfo.progress = nil) or (cinfo.progress^.instance = nil) then Exit;
with cinfo.progress^ do
begin
Ticks := GetTickCount;
if (Ticks - last_time) < cinfo.progress^.instance.fProgressTime 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 := MakeRect(0, last_scanline, output_width, output_scanline);
if R.Bottom < last_scanline then
R.Bottom := output_height;
end
else
R := MakeRect(0,0,0,0);
temp := Integer(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, '');
if instance.FProgress( instance, R ) then
instance.FStop := TRUE;
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: PJpeg; 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.progress_monitor := FCallback;
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;
{ TJpeg }
function DummyProgress( JPEGobj: PJpeg; const R: TRect ): Boolean;
begin
Result := FALSE; // not stop
end;
function NormalProgress( JPEGobj: PJpeg; const R: TRect ): Boolean;
begin
Result := FALSE; // not stop
if Assigned( JPEGobj.FOnProgress ) then
JPEGobj.FOnProgress( JPEGobj, R, Result );
end;
function NewJpeg: PJpeg;
begin
new( Result, Create );
with Result^ do
begin
CreateImage;
FQuality := JPEGDefaults.CompressionQuality;
FGrayscale := JPEGDefaults.Grayscale;
FPerformance := JPEGDefaults.Performance;
FPixelFormat := JPEGDefaults.PixelFormat;
FProgressiveDisplay := JPEGDefaults.ProgressiveDisplay;
FProgressiveEncoding := JPEGDefaults.ProgressiveEncoding;
FScale := JPEGDefaults.Scale;
FSmoothing := JPEGDefaults.Smoothing;
FProgress := DummyProgress;
FCallback := @ DummyProgressCallback;
fProgressTime := 100;
end;
end;
procedure TJpeg.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 TJpeg.Clear;
begin
FreeBitmap;
FImage.Clear;
end;
procedure TJpeg.Compress;
var
LinesWritten, LinesPerCall: Integer;
SrcScanLine: Pointer;
PtrInc: Integer;
jc: TJPEGContext;
Src: PBitmap;
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.progress_monitor := FCallback;
jc.progress.instance := @Self;
jc.common.progress := @jc.progress;
if FImage.FData <> nil then CreateImage;
FImage.FData := NewMemoryStream;
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 := NewBitmap( 0, 0 );
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 := 0;
//if jc.d.output_height > 1 then
if FImage.FHeight > 1 then
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), '');
FProgress( @Self, MakeRect( 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), '');
//FProgress( @Self, MakeRect( 0, 0, 0, 0 ) );
end;
finally
Src.Free;
end;
{$IFDEF JPEGERR}
except
//on EAbort do // OnProgress can raise EAbort to cancel image save
CreateImage; // Throw away any partial jpg data
{$ELSE}
finally {+}
{$ENDIF}
end;
finally
ReleaseContext(jc);
end;
end;
destructor TJpeg.Destroy;
begin
if FTempPal <> 0 then DeleteObject(FTempPal);
FBitmap.Free;
FImage.Free;
inherited;
end;
procedure TJpeg.DIBNeeded;
begin
GetBitmap;
end;
procedure TJpeg.Draw(DC: HDC; X, Y: Integer);
begin
if Assigned(Bitmap) then
Bitmap.Draw( DC, X, Y );
end;
procedure TJpeg.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^[0]^[I];
Pal.palPalEntry[I].peGreen := cinfo.colormap^[1]^[I];
Pal.palPalEntry[I].peBlue := cinfo.colormap^[2]^[I];
// 23 Jun 2005: R <-> B fixed (reporter: Sapersky)
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.common.mem.alloc_sarray(@cinfo.common, 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;
procedure SetBitmapDIBPalette( Bmp: PBitmap; Pal: HPalette );
var Entries: array[ 0..255 ] of Integer;
I: Integer;
begin
GetPaletteEntries( Pal, 0, 256, Entries[ 0 ] );
for I := 0 to 255 do
Bmp.DIBPalEntries[ I ] := Entries[ I ];
end;
function TJpeg.GetBitmap: PBitmap;
var
LinesPerCall, LinesRead: Integer;
DestScanLine: Pointer;
PtrInc: Integer;
jc: TJPEGContext;
GeneratePalette: Boolean;
PaletteModified : Boolean;
TmpPal: HPalette;
OK: Boolean;
begin
Result := FBitmap;
if Result <> nil then Exit;
if (Width = 0) or (Height = 0) then
Exit;
GeneratePalette := True;
{$IFNDEF JPEGERR}
Jpeg_Error := FALSE;
{$ENDIF}
FStop := FALSE;
InitDecompressor(@Self, jc);
{$IFNDEF JPEGERR}
FCorrupted := Jpeg_Error;
{$ENDIF}
//++++++
FBitmap.Free;
if (PixelFormat = jf8Bit) or (jc.d.out_color_space = JCS_GRAYSCALE) then
FBitmap := NewDIBBitmap( Width, Height, pf8bit )
else
begin
if jc.d.out_color_space in [JCS_CMYK,JCS_YCCK] then
FBitmap := NewDIBBitmap( Width, Height, pf32bit )
//jc.d.out_color_space := JCS_RGB;
else
FBitmap := NewDIBBitmap( Width, Height, pf24bit );
end;
Result := FBitmap;
//++++++
try
try
// Set the bitmap pixel format
//--Progress(Self, psStarting, 0, False, Rect(0,0,0,0), '');
FProgress( @Self, MakeRect( 0, 0, 0, 0 ) );
PaletteModified := False;
OK := FALSE;
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
//---------------------------------------
SetBitmapDIBPalette( FBitmap, FTempPal );
GeneratePalette := False;
end
else
begin
DeleteObject(FTempPal);
FTempPal := 0;
end;
end;
jpeg_start_decompress(jc.d);
//{$IFNDEF JPEGERR}
//if Jpeg_Error then Exit;
//{$ENDIF}
// 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 := 0;
if jc.d.output_height > 1 then
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);
{$IFNDEF JPEGERR}
if Jpeg_Error then Exit;
{$ENDIF}
if FStop then Exit;
// 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);
///////////////////////////////////////////
TmpPal := BuildPalette(jc.d); //
SetBitmapDIBPalette( FBitmap, TmpPal ); //
DeleteObject( TmpPal ); //
///////////////////////////////////////////
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);
///////////////////////////////////////////
TmpPal := BuildPalette(jc.d); //
SetBitmapDIBPalette( FBitmap, TmpPal ); //
DeleteObject( TmpPal ); //
///////////////////////////////////////////
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);
OK := TRUE;
finally
{--
if ExceptObject = nil then
PtrInc := 100
else
PtrInc := 0;
--}
//--Progress(Self, psEnding, PtrInc, PaletteModified, Rect(0,0,0,0), '');
FProgress( @Self, MakeRect( 0, 0, Width, Height ) );
// Make sure new palette gets realized, in case OnProgress event didn't.
if PaletteModified then
Changed;
Jpeg_Error := Jpeg_Error or not OK;
if jc.d.out_color_space in [JCS_CMYK,JCS_YCCK] then
begin
FCMYK := TRUE;
if Assigned( FConvertCMYK2RGBProc ) then
begin
FConvertCMYK2RGBProc( FBitmap );
FCMYK := FALSE;
end;
end;
end;
except
//--on EAbort do ; // OnProgress can raise EAbort to cancel image load
{$IFDEF JPEGERR}
FCorrupted := TRUE;
{$ENDIF}
end;
finally
ReleaseContext(jc);
{$IFNDEF JPEGERR}
FCorrupted := Jpeg_Error;
{$ENDIF}
end;
end;
function TJpeg.GetEmpty: Boolean;
begin
Result := (Width = 0) or (Height = 0);
{Result := (FImage.FData = nil) and
((FBitmap = nil) or FBitmap.Empty);}
end;
function TJpeg.GetGrayscale: Boolean;
begin
Result := FGrayscale or FImage.FGrayscale;
end;
function TJpeg.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 TJpeg.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
FTempPal := CreateHalftonePalette(DC);
Result := FTempPal;
end;
ReleaseDC(0, DC);
end;
end;
function TJpeg.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 TJpeg.JPEGNeeded;
begin
if FImage.FData = nil then
Compress;
end;
procedure TJpeg.LoadFromStream(Stream: PStream);
begin
FCorrupted := FALSE;
ReadStream(Stream.Size - Stream.Position, Stream);
end;
procedure TJpeg.CreateBitmap;
begin
FBitmap.Free;
FBitmap := NewBitmap(0, 0);
end;
procedure TJpeg.CreateImage;
begin
FImage.Free;
new( FImage, Create );
end;
procedure TJpeg.ReadData(Stream: PStream);
var Size: Integer;
begin
Stream.Read(Size, SizeOf(Size));
ReadStream(Size, Stream);
end;
procedure TJpeg.ReadStream(Size: Integer; Stream: PStream);
var
jerr: jpeg_error_mgr;
cinfo: jpeg_decompress_struct;
begin
CreateImage;
FreeBitmap;
try
with FImage^ do
begin
FData := NewMemoryStream;
FData.Size := Size;
Stream.Read(FData.Memory^, Size);
if Size > 0 {SizeOf(cinfo)} then
begin
jerr := jpeg_std_error; // use local var for thread isolation
cinfo.common.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;
except
CreateImage;
//FreeBitmap;
end;
Changed;
end;
procedure TJpeg.SaveToStream(Stream: PStream);
begin
JPEGNeeded;
with FImage.FData^ do
Stream.Write(Memory^, Size);
end;
procedure TJpeg.SetGrayscale(Value: Boolean);
begin
if FGrayscale <> Value then
begin
FreeBitmap;
FGrayscale := Value;
//--PaletteModified := True;
Changed;
end;
end;
procedure TJpeg.SetHeight(Value: Integer);
begin
InvalidOperation( 'Could not set height for JPEG image' );
end;
procedure TJpeg.SetPalette(Value: HPalette);
var SignalChange: Boolean;
begin
if Value <> FTempPal then
begin
SignalChange := (FBitmap <> nil); //and (Value <> FBitmap.Palette);
if SignalChange then FreeBitmap;
if FTempPal <> 0 then DeleteObject(FTempPal);
FTempPal := Value;
if SignalChange then
begin
//PaletteModified := True;
Changed;
end;
end;
end;
procedure TJpeg.SetPerformance(Value: TJPEGPerformance);
begin
if FPerformance <> Value then
begin
FreeBitmap;
FPerformance := Value;
//--PaletteModified := True;
Changed;
end;
end;
procedure TJpeg.SetPixelFormat(Value: TJPEGPixelFormat);
begin
if FPixelFormat <> Value then
begin
{
FreeBitmap;
FPixelFormat := Value;
//--PaletteModified := True;
Changed;
}
DIBNeeded;
FImage.FData.Free;
FImage.FData:= nil;
FPixelFormat := Value;
JPEGNeeded;
FreeBitmap;
Changed;
end;
end;
procedure TJpeg.SetScale(Value: TJPEGScale);
begin
if FScale <> Value then
begin
FreeBitmap;
FScale := Value;
FNeedRecalc := True;
Changed;
end;
end;
procedure TJpeg.SetSmoothing(Value: Boolean);
begin
if FSmoothing <> Value then
begin
FreeBitmap;
FSmoothing := Value;
Changed;
end;
end;
procedure TJpeg.SetWidth(Value: Integer);
begin
InvalidOperation( 'Could not set width for JPEG image' );
end;
procedure TJpeg.StretchDraw(DC: HDC; Dest: TRect);
begin
Bitmap.StretchDraw( DC, Dest );
end;
procedure TJpeg.WriteData(Stream: PStream);
var
Size: Integer;
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;
function TJpeg.LoadFromFile( const FName : String ) : Boolean;
var Strm : PStream;
begin
Clear;
Strm := NewReadFileStream( FName );
if Strm.Size > 0 then
LoadFromStream( Strm );
Strm.Free;
Result := not Empty;
end;
function TJpeg.SaveToFile( const FName : String ) : Boolean;
var Strm : PStream;
begin
Result := False;
if Empty then Exit;
Strm := NewWriteFileStream( FName );
SaveToStream( Strm );
Result := Strm.Position > 0;
Strm.Free;
end;
procedure TJpeg.Changed;
begin
if Assigned( OnChange ) then
OnChange( @Self );
end;
{function TJpeg.Empty: Boolean;
begin
Result := (Width = 0) or (Height = 0);
end;}
procedure TJpeg.SetOnProgress(const Value: TJPEGProgress);
begin
FOnProgress := Value;
FProgress := NormalProgress;
FCallback := @ ProgressCallback;
end;
procedure TJpeg.SetBitmap(const Value: PBitmap);
begin
CreateImage;
CreateBitmap;
{FBitmap :=} FBitmap.Assign( Value );
Changed;
end;
procedure DoConvertCMYK2RGB( Bmp: PBitmap );
var I, J: Integer;
C, M, Y, K, R, G, B: Integer;
P: PDWORD;
begin
if Bmp.PixelFormat <> pf32bit then Exit;
for I := 0 to Bmp.Height-1 do
begin
P := Bmp.ScanLine[ I ];
for J := 0 to Bmp.Width - 1 do
begin
C := P^ and $FF;
M := (P^ shr 8) and $FF;
Y := (P^ shr 16) and $FF;
K := P^ shr 24;
R := Y * K div 255;
G := M * K div 255;
B := C * K div 255;
P^ := R or (G shl 8) or (B shl 16);
Inc( P );
end;
end;
end;
procedure TJpeg.SetConvertCMYK2RGB(const Value: Boolean);
begin
FConvertCMYK2RGB := Value;
if TRUE then
begin
FConvertCMYK2RGBProc := DoConvertCMYK2RGB;
if (FBitmap <> nil) and FCMYK and
(FBitmap.Width > 0) and (FBitmap.Height > 0) then
DoConvertCMYK2RGB( FBitmap );
end
else
begin
FConvertCMYK2RGBProc := nil;
end;
end;
end.