578 lines
17 KiB
Plaintext
Executable File
578 lines
17 KiB
Plaintext
Executable File
create or replace package body image_util_pkg
|
|
as
|
|
|
|
/*
|
|
|
|
Purpose: Package handles images
|
|
|
|
Remarks: Based on image parsing code from Anton Scheffer's AS_PDF3 package
|
|
|
|
Who Date Description
|
|
------ ---------- --------------------------------
|
|
MBR 23.06.2012 Created
|
|
|
|
*/
|
|
|
|
type tp_pls_tab is table of pls_integer index by pls_integer;
|
|
|
|
|
|
function blob2num (p_blob blob,
|
|
p_len integer,
|
|
p_pos integer) return number
|
|
is
|
|
begin
|
|
return to_number( rawtohex( dbms_lob.substr( p_blob, p_len, p_pos ) ), 'xxxxxxxx' );
|
|
end blob2num;
|
|
|
|
|
|
function raw2num (p_value raw) return number
|
|
is
|
|
begin
|
|
return to_number( rawtohex( p_value ), 'XXXXXXXX' );
|
|
end raw2num;
|
|
|
|
|
|
function raw2num (p_value raw,
|
|
p_pos pls_integer,
|
|
p_len pls_integer) return pls_integer
|
|
is
|
|
begin
|
|
return to_number( rawtohex( utl_raw.substr( p_value, p_pos, p_len ) ), 'XXXXXXXX' );
|
|
end raw2num;
|
|
|
|
|
|
function adler32 (p_src in blob) return varchar2
|
|
is
|
|
s1 pls_integer := 1;
|
|
s2 pls_integer := 0;
|
|
n pls_integer;
|
|
step_size number;
|
|
tmp varchar2(32766);
|
|
c65521 constant pls_integer := 65521;
|
|
begin
|
|
|
|
step_size := trunc( 16383 / dbms_lob.getchunksize( p_src ) ) * dbms_lob.getchunksize( p_src );
|
|
for j in 0 .. trunc( ( dbms_lob.getlength( p_src ) - 1 ) / step_size )
|
|
loop
|
|
tmp := rawtohex( dbms_lob.substr( p_src, step_size, j * step_size + 1 ) );
|
|
for i in 1 .. length( tmp ) / 2
|
|
loop
|
|
n := to_number( substr( tmp, i * 2 - 1, 2 ), 'xx' );
|
|
s1 := s1 + n;
|
|
if s1 >= c65521
|
|
then
|
|
s1 := s1 - c65521;
|
|
end if;
|
|
s2 := s2 + s1;
|
|
if s2 >= c65521
|
|
then
|
|
s2 := s2 - c65521;
|
|
end if;
|
|
end loop;
|
|
end loop;
|
|
return to_char( s2, 'fm0XXX' ) || to_char( s1, 'fm0XXX' );
|
|
|
|
end adler32;
|
|
|
|
|
|
function parse_jpg (p_img_blob blob) return t_image_info
|
|
is
|
|
buf raw(4);
|
|
t_img t_image_info;
|
|
t_ind integer;
|
|
begin
|
|
|
|
/*
|
|
|
|
Purpose: Parse JPG
|
|
|
|
Remarks: From Anton Scheffer's AS_PDF3 package
|
|
|
|
Who Date Description
|
|
------ ---------- --------------------------------
|
|
MBR 23.06.2012 Created
|
|
|
|
*/
|
|
|
|
if ( dbms_lob.substr( p_img_blob, 2, 1 ) != hextoraw( 'FFD8' ) -- SOI Start of Image
|
|
or dbms_lob.substr( p_img_blob, 2, dbms_lob.getlength( p_img_blob ) - 1 ) != hextoraw( 'FFD9' ) -- EOI End of Image
|
|
)
|
|
then -- this is not a jpg I can handle
|
|
return null;
|
|
end if;
|
|
--
|
|
t_img.pixels := p_img_blob;
|
|
t_img.type := 'jpg';
|
|
if dbms_lob.substr( t_img.pixels, 2, 3 ) in ( hextoraw( 'FFE0' ) -- a APP0 jpg
|
|
, hextoraw( 'FFE1' ) -- a APP1 jpg
|
|
)
|
|
then
|
|
t_img.color_res := 8;
|
|
t_img.height := 1;
|
|
t_img.width := 1;
|
|
--
|
|
t_ind := 3;
|
|
t_ind := t_ind + 2 + blob2num( t_img.pixels, 2, t_ind + 2 );
|
|
loop
|
|
buf := dbms_lob.substr( t_img.pixels, 2, t_ind );
|
|
exit when buf = hextoraw( 'FFDA' ); -- SOS Start of Scan
|
|
exit when buf = hextoraw( 'FFD9' ); -- EOI End Of Image
|
|
exit when substr( rawtohex( buf ), 1, 2 ) != 'FF';
|
|
if rawtohex( buf ) in ( 'FFD0' -- RSTn
|
|
, 'FFD1', 'FFD2', 'FFD3', 'FFD4', 'FFD5', 'FFD6', 'FFD7', 'FF01' -- TEM
|
|
)
|
|
then
|
|
t_ind := t_ind + 2;
|
|
else
|
|
if buf = hextoraw( 'FFC0' ) -- SOF0 (Start Of Frame 0) marker
|
|
then
|
|
t_img.color_res := blob2num( t_img.pixels, 1, t_ind + 4 );
|
|
t_img.height := blob2num( t_img.pixels, 2, t_ind + 5 );
|
|
t_img.width := blob2num( t_img.pixels, 2, t_ind + 7 );
|
|
end if;
|
|
t_ind := t_ind + 2 + blob2num( t_img.pixels, 2, t_ind + 2 );
|
|
end if;
|
|
end loop;
|
|
end if;
|
|
--
|
|
return t_img;
|
|
|
|
end parse_jpg;
|
|
|
|
|
|
function parse_png (p_img_blob blob) return t_image_info
|
|
is
|
|
t_img t_image_info;
|
|
buf raw(32767);
|
|
len integer;
|
|
ind integer;
|
|
color_type pls_integer;
|
|
begin
|
|
|
|
/*
|
|
|
|
Purpose: Parse PNG
|
|
|
|
Remarks: From Anton Scheffer's AS_PDF3 package
|
|
|
|
Who Date Description
|
|
------ ---------- --------------------------------
|
|
MBR 23.06.2012 Created
|
|
|
|
*/
|
|
|
|
if rawtohex( dbms_lob.substr( p_img_blob, 8, 1 ) ) != '89504E470D0A1A0A' -- not the right signature
|
|
then
|
|
return null;
|
|
end if;
|
|
dbms_lob.createtemporary( t_img.pixels, true );
|
|
ind := 9;
|
|
loop
|
|
len := blob2num( p_img_blob, 4, ind ); -- length
|
|
exit when len is null or ind > dbms_lob.getlength( p_img_blob );
|
|
case utl_raw.cast_to_varchar2( dbms_lob.substr( p_img_blob, 4, ind + 4 ) ) -- Chunk type
|
|
when 'IHDR'
|
|
then
|
|
t_img.width := blob2num( p_img_blob, 4, ind + 8 );
|
|
t_img.height := blob2num( p_img_blob, 4, ind + 12 );
|
|
t_img.color_res := blob2num( p_img_blob, 1, ind + 16 );
|
|
color_type := blob2num( p_img_blob, 1, ind + 17 );
|
|
t_img.greyscale := color_type in ( 0, 4 );
|
|
when 'PLTE'
|
|
then
|
|
t_img.color_tab := dbms_lob.substr( p_img_blob, len, ind + 8 );
|
|
when 'IDAT'
|
|
then
|
|
dbms_lob.copy( t_img.pixels, p_img_blob, len, dbms_lob.getlength( t_img.pixels ) + 1, ind + 8 );
|
|
when 'IEND'
|
|
then
|
|
exit;
|
|
else
|
|
null;
|
|
end case;
|
|
ind := ind + 4 + 4 + len + 4; -- Length + Chunk type + Chunk data + CRC
|
|
end loop;
|
|
--
|
|
t_img.type := g_format_png;
|
|
t_img.nr_colors := case color_type
|
|
when 0 then 1
|
|
when 2 then 3
|
|
when 3 then 1
|
|
when 4 then 2
|
|
else 4
|
|
end;
|
|
--
|
|
return t_img;
|
|
|
|
end parse_png;
|
|
|
|
|
|
function lzw_decompress (p_blob blob,
|
|
p_bits pls_integer) return blob
|
|
is
|
|
|
|
powers tp_pls_tab;
|
|
--
|
|
g_lzw_ind pls_integer;
|
|
g_lzw_bits pls_integer;
|
|
g_lzw_buffer pls_integer;
|
|
g_lzw_bits_used pls_integer;
|
|
--
|
|
type tp_lzw_dict is table of raw(1000) index by pls_integer;
|
|
t_lzw_dict tp_lzw_dict;
|
|
t_clr_code pls_integer;
|
|
t_nxt_code pls_integer;
|
|
t_new_code pls_integer;
|
|
t_old_code pls_integer;
|
|
t_blob blob;
|
|
--
|
|
function get_lzw_code
|
|
return pls_integer
|
|
is
|
|
t_rv pls_integer;
|
|
begin
|
|
while g_lzw_bits_used < g_lzw_bits
|
|
loop
|
|
g_lzw_ind := g_lzw_ind + 1;
|
|
g_lzw_buffer := blob2num( p_blob, 1, g_lzw_ind ) * powers( g_lzw_bits_used ) + g_lzw_buffer;
|
|
g_lzw_bits_used := g_lzw_bits_used + 8;
|
|
end loop;
|
|
t_rv := bitand( g_lzw_buffer, powers( g_lzw_bits ) - 1 );
|
|
g_lzw_bits_used := g_lzw_bits_used - g_lzw_bits;
|
|
g_lzw_buffer := trunc( g_lzw_buffer / powers( g_lzw_bits ) );
|
|
return t_rv;
|
|
end;
|
|
--
|
|
begin
|
|
|
|
/*
|
|
|
|
Purpose: LZW decompression
|
|
|
|
Remarks: From Anton Scheffer's AS_PDF3 package
|
|
|
|
Who Date Description
|
|
------ ---------- --------------------------------
|
|
MBR 23.06.2012 Created
|
|
|
|
*/
|
|
|
|
for i in 0 .. 30
|
|
loop
|
|
powers( i ) := power( 2, i );
|
|
end loop;
|
|
--
|
|
t_clr_code := powers( p_bits - 1 );
|
|
t_nxt_code := t_clr_code + 2;
|
|
for i in 0 .. least( t_clr_code - 1, 255 )
|
|
loop
|
|
t_lzw_dict( i ) := hextoraw( to_char( i, 'fm0X' ) );
|
|
end loop;
|
|
dbms_lob.createtemporary( t_blob, true );
|
|
g_lzw_ind := 0;
|
|
g_lzw_bits := p_bits;
|
|
g_lzw_buffer := 0;
|
|
g_lzw_bits_used := 0;
|
|
--
|
|
t_old_code := null;
|
|
t_new_code := get_lzw_code( );
|
|
loop
|
|
case nvl( t_new_code, t_clr_code + 1 )
|
|
when t_clr_code + 1
|
|
then
|
|
exit;
|
|
when t_clr_code
|
|
then
|
|
t_new_code := null;
|
|
g_lzw_bits := p_bits;
|
|
t_nxt_code := t_clr_code + 2;
|
|
else
|
|
if t_new_code = t_nxt_code
|
|
then
|
|
t_lzw_dict( t_nxt_code ) :=
|
|
utl_raw.concat( t_lzw_dict( t_old_code )
|
|
, utl_raw.substr( t_lzw_dict( t_old_code ), 1, 1 )
|
|
);
|
|
dbms_lob.append( t_blob, t_lzw_dict( t_nxt_code ) );
|
|
t_nxt_code := t_nxt_code + 1;
|
|
elsif t_new_code > t_nxt_code
|
|
then
|
|
exit;
|
|
else
|
|
dbms_lob.append( t_blob, t_lzw_dict( t_new_code ) );
|
|
if t_old_code is not null
|
|
then
|
|
t_lzw_dict( t_nxt_code ) := utl_raw.concat( t_lzw_dict( t_old_code )
|
|
, utl_raw.substr( t_lzw_dict( t_new_code ), 1, 1 )
|
|
);
|
|
t_nxt_code := t_nxt_code + 1;
|
|
end if;
|
|
end if;
|
|
if bitand( t_nxt_code, powers( g_lzw_bits ) - 1 ) = 0
|
|
and g_lzw_bits < 12
|
|
then
|
|
g_lzw_bits := g_lzw_bits + 1;
|
|
end if;
|
|
end case;
|
|
t_old_code := t_new_code;
|
|
t_new_code := get_lzw_code( );
|
|
end loop;
|
|
t_lzw_dict.delete;
|
|
--
|
|
return t_blob;
|
|
|
|
end lzw_decompress;
|
|
|
|
|
|
function parse_gif (p_img_blob blob) return t_image_info
|
|
is
|
|
img t_image_info;
|
|
buf raw(4000);
|
|
ind integer;
|
|
t_len pls_integer;
|
|
begin
|
|
|
|
/*
|
|
|
|
Purpose: Parse GIF
|
|
|
|
Remarks: From Anton Scheffer's AS_PDF3 package
|
|
|
|
Who Date Description
|
|
------ ---------- --------------------------------
|
|
MBR 23.06.2012 Created
|
|
|
|
*/
|
|
|
|
if dbms_lob.substr( p_img_blob, 3, 1 ) != utl_raw.cast_to_raw( 'GIF' )
|
|
then
|
|
return null;
|
|
end if;
|
|
ind := 7;
|
|
buf := dbms_lob.substr( p_img_blob, 7, 7 ); -- Logical Screen Descriptor
|
|
ind := ind + 7;
|
|
img.color_res := raw2num( utl_raw.bit_and( utl_raw.substr( buf, 5, 1 ), hextoraw( '70' ) ) ) / 16 + 1;
|
|
img.color_res := 8;
|
|
if raw2num( buf, 5, 1 ) > 127
|
|
then
|
|
t_len := 3 * power( 2, raw2num( utl_raw.bit_and( utl_raw.substr( buf, 5, 1 ), hextoraw( '07' ) ) ) + 1 );
|
|
img.color_tab := dbms_lob.substr( p_img_blob, t_len, ind ); -- Global Color Table
|
|
ind := ind + t_len;
|
|
end if;
|
|
--
|
|
loop
|
|
case dbms_lob.substr( p_img_blob, 1, ind )
|
|
when hextoraw( '3B' ) -- trailer
|
|
then
|
|
exit;
|
|
when hextoraw( '21' ) -- extension
|
|
then
|
|
if dbms_lob.substr( p_img_blob, 1, ind + 1 ) = hextoraw( 'F9' )
|
|
then -- Graphic Control Extension
|
|
if utl_raw.bit_and( dbms_lob.substr( p_img_blob, 1, ind + 3 ), hextoraw( '01' ) ) = hextoraw( '01' )
|
|
then -- Transparent Color Flag set
|
|
img.transparency_index := blob2num( p_img_blob, 1, ind + 6 );
|
|
end if;
|
|
end if;
|
|
ind := ind + 2; -- skip sentinel + label
|
|
loop
|
|
t_len := blob2num( p_img_blob, 1, ind ); -- Block Size
|
|
exit when t_len = 0;
|
|
ind := ind + 1 + t_len; -- skip Block Size + Data Sub-block
|
|
end loop;
|
|
ind := ind + 1; -- skip last Block Size
|
|
when hextoraw( '2C' ) -- image
|
|
then
|
|
declare
|
|
img_blob blob;
|
|
min_code_size pls_integer;
|
|
code_size pls_integer;
|
|
flags raw(1);
|
|
begin
|
|
img.width := utl_raw.cast_to_binary_integer( dbms_lob.substr( p_img_blob, 2, ind + 5 )
|
|
, utl_raw.little_endian
|
|
);
|
|
img.height := utl_raw.cast_to_binary_integer( dbms_lob.substr( p_img_blob, 2, ind + 7 )
|
|
, utl_raw.little_endian
|
|
);
|
|
img.greyscale := false;
|
|
ind := ind + 1 + 8; -- skip sentinel + img sizes
|
|
flags := dbms_lob.substr( p_img_blob, 1, ind );
|
|
if utl_raw.bit_and( flags, hextoraw( '80' ) ) = hextoraw( '80' )
|
|
then
|
|
t_len := 3 * power( 2, raw2num( utl_raw.bit_and( flags, hextoraw( '07' ) ) ) + 1 );
|
|
img.color_tab := dbms_lob.substr( p_img_blob, t_len, ind + 1 ); -- Local Color Table
|
|
end if;
|
|
ind := ind + 1; -- skip image Flags
|
|
min_code_size := blob2num( p_img_blob, 1, ind );
|
|
ind := ind + 1; -- skip LZW Minimum Code Size
|
|
dbms_lob.createtemporary( img_blob, true );
|
|
loop
|
|
t_len := blob2num( p_img_blob, 1, ind ); -- Block Size
|
|
exit when t_len = 0;
|
|
dbms_lob.append( img_blob, dbms_lob.substr( p_img_blob, t_len, ind + 1 ) ); -- Data Sub-block
|
|
ind := ind + 1 + t_len; -- skip Block Size + Data Sub-block
|
|
end loop;
|
|
ind := ind + 1; -- skip last Block Size
|
|
img.pixels := lzw_decompress( img_blob, min_code_size + 1 );
|
|
--
|
|
if utl_raw.bit_and( flags, hextoraw( '40' ) ) = hextoraw( '40' )
|
|
then -- interlaced
|
|
declare
|
|
pass pls_integer;
|
|
pass_ind tp_pls_tab;
|
|
l_mod number;
|
|
begin
|
|
dbms_lob.createtemporary( img_blob, true );
|
|
pass_ind( 1 ) := 1;
|
|
pass_ind( 2 ) := trunc( ( img.height - 1 ) / 8 ) + 1;
|
|
pass_ind( 3 ) := pass_ind( 2 ) + trunc( ( img.height + 3 ) / 8 );
|
|
pass_ind( 4 ) := pass_ind( 3 ) + trunc( ( img.height + 1 ) / 4 );
|
|
pass_ind( 2 ) := pass_ind( 2 ) * img.width + 1;
|
|
pass_ind( 3 ) := pass_ind( 3 ) * img.width + 1;
|
|
pass_ind( 4 ) := pass_ind( 4 ) * img.width + 1;
|
|
for i in 0 .. img.height - 1
|
|
loop
|
|
l_mod := mod( i, 8 );
|
|
pass := case l_mod
|
|
when 0 then 1
|
|
when 4 then 2
|
|
when 2 then 3
|
|
when 6 then 3
|
|
else 4
|
|
end;
|
|
dbms_lob.append( img_blob, dbms_lob.substr( img.pixels, img.width, pass_ind( pass ) ) );
|
|
pass_ind( pass ) := pass_ind( pass ) + img.width;
|
|
end loop;
|
|
img.pixels := img_blob;
|
|
end;
|
|
end if;
|
|
--
|
|
dbms_lob.freetemporary( img_blob );
|
|
end;
|
|
else
|
|
exit;
|
|
end case;
|
|
end loop;
|
|
--
|
|
img.type := g_format_gif;
|
|
return img;
|
|
|
|
end parse_gif;
|
|
|
|
|
|
function parse_img (p_blob in blob,
|
|
p_adler32 in varchar2 := null,
|
|
p_type in varchar2 := null) return t_image_info
|
|
is
|
|
t_img t_image_info;
|
|
begin
|
|
|
|
/*
|
|
|
|
Purpose: Parse image file
|
|
|
|
Remarks: From Anton Scheffer's AS_PDF3 package
|
|
|
|
Who Date Description
|
|
------ ---------- --------------------------------
|
|
MBR 23.06.2012 Created
|
|
|
|
*/
|
|
|
|
t_img.type := p_type;
|
|
if t_img.type is null
|
|
then
|
|
if rawtohex( dbms_lob.substr( p_blob, 8, 1 ) ) = '89504E470D0A1A0A'
|
|
then
|
|
t_img.type := g_format_png;
|
|
elsif dbms_lob.substr( p_blob , 3, 1 ) = utl_raw.cast_to_raw( 'GIF' )
|
|
then
|
|
t_img.type := g_format_gif;
|
|
else
|
|
t_img.type := g_format_jpg;
|
|
end if;
|
|
end if;
|
|
--
|
|
t_img := case lower( t_img.type )
|
|
when g_format_gif then parse_gif( p_blob )
|
|
when g_format_png then parse_png( p_blob )
|
|
when g_format_jpg then parse_jpg( p_blob )
|
|
else null
|
|
end;
|
|
--
|
|
if t_img.type is not null
|
|
then
|
|
t_img.adler32 := coalesce( p_adler32, adler32( p_blob ) );
|
|
end if;
|
|
return t_img;
|
|
|
|
end parse_img;
|
|
|
|
|
|
|
|
function is_image (p_file in blob,
|
|
p_format in varchar2 := null) return boolean
|
|
as
|
|
l_info t_image_info;
|
|
l_returnvalue boolean := false;
|
|
begin
|
|
|
|
/*
|
|
|
|
Purpose: returns true if blob is image
|
|
|
|
Remarks:
|
|
|
|
Who Date Description
|
|
------ ---------- --------------------------------
|
|
MBR 23.06.2012 Created
|
|
|
|
*/
|
|
|
|
l_info := get_image_info (p_file);
|
|
|
|
if l_info.type is not null then
|
|
|
|
if p_format is null then
|
|
l_returnvalue := l_info.type in (g_format_jpg, g_format_png, g_format_gif);
|
|
else
|
|
l_returnvalue := l_info.type = p_format;
|
|
end if;
|
|
|
|
end if;
|
|
|
|
return l_returnvalue;
|
|
|
|
end is_image;
|
|
|
|
|
|
function get_image_info (p_file in blob) return t_image_info
|
|
as
|
|
l_returnvalue t_image_info;
|
|
begin
|
|
|
|
/*
|
|
|
|
Purpose: get image information
|
|
|
|
Remarks:
|
|
|
|
Who Date Description
|
|
------ ---------- --------------------------------
|
|
MBR 23.06.2012 Created
|
|
|
|
*/
|
|
|
|
l_returnvalue := parse_img (p_file);
|
|
|
|
return l_returnvalue;
|
|
|
|
end get_image_info;
|
|
|
|
end image_util_pkg;
|
|
/
|
|
|
|
|
|
|